From 1c8475b5a343757a808437cb1e55449189ffdec9 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Tue, 9 Jan 2024 16:35:08 +0100 Subject: [PATCH 001/101] IO_DXF: refactor reading of SPLINE entities --- src/io_dxf/dxf.cpp | 104 ++++++++----------------- src/io_dxf/dxf.h | 73 +++++++++++------- src/io_dxf/io_dxf.cpp | 173 +++++++++++++++++++++--------------------- 3 files changed, 166 insertions(+), 184 deletions(-) diff --git a/src/io_dxf/dxf.cpp b/src/io_dxf/dxf.cpp index ab432b5b..03ac930c 100644 --- a/src/io_dxf/dxf.cpp +++ b/src/io_dxf/dxf.cpp @@ -2170,13 +2170,10 @@ bool CDxfRead::ReadArc() bool CDxfRead::ReadSpline() { - struct SplineData sd; - sd.norm = {0., 0., 1.}; - sd.degree = 0; - sd.knots = 0; - sd.flag = 0; - sd.control_points = 0; - sd.fit_points = 0; + Dxf_SPLINE spline; + int knotCount = 0; + int controlPointCount = 0; + int fitPointCount = 0; while (!m_ifs.eof()) { get_line(); @@ -2184,7 +2181,7 @@ bool CDxfRead::ReadSpline() if (n == 0) { // next item found, so finish with Spline ResolveColorIndex(); - OnReadSpline(sd); + OnReadSpline(spline); return true; } else if (isStringToErrorValue(n)) { @@ -2194,92 +2191,53 @@ bool CDxfRead::ReadSpline() get_line(); switch (n) { - case 210: - case 220: - case 230: - // normal coords - HandleCoordCode<210, 220, 230>(n, &sd.norm); + case 210: case 220: case 230: + HandleCoordCode<210, 220, 230>(n, &spline.normalVector); break; case 70: - // flag - sd.flag = stringToInt(m_str); + spline.flags = stringToUnsigned(m_str); break; case 71: - // degree - sd.degree = stringToInt(m_str); + spline.degree = stringToInt(m_str); break; case 72: - // knots - sd.knots = stringToInt(m_str); + knotCount = stringToInt(m_str); + spline.knots.reserve(knotCount); break; case 73: - // control points - sd.control_points = stringToInt(m_str); + controlPointCount = stringToInt(m_str); + spline.controlPoints.reserve(controlPointCount); break; case 74: - // fit points - sd.fit_points = stringToInt(m_str); - break; - case 12: - // starttan x - sd.starttanx.push_back(mm(stringToDouble(m_str))); - break; - case 22: - // starttan y - sd.starttany.push_back(mm(stringToDouble(m_str))); + fitPointCount = stringToInt(m_str); + spline.fitPoints.reserve(fitPointCount); break; - case 32: - // starttan z - sd.starttanz.push_back(mm(stringToDouble(m_str))); - break; - case 13: - // endtan x - sd.endtanx.push_back(mm(stringToDouble(m_str))); - break; - case 23: - // endtan y - sd.endtany.push_back(mm(stringToDouble(m_str))); + case 12: case 22: case 32: + HandleVectorCoordCode<12, 22, 32>(n, &spline.startTangents); break; - case 33: - // endtan z - sd.endtanz.push_back(mm(stringToDouble(m_str))); + case 13: case 23: case 33: + HandleVectorCoordCode<13, 23, 33>(n, &spline.endTangents); break; case 40: - // knot - sd.knot.push_back(mm(stringToDouble(m_str))); + spline.knots.push_back(mm(stringToDouble(m_str))); break; case 41: - // weight - sd.weight.push_back(mm(stringToDouble(m_str))); - break; - case 10: - // control x - sd.controlx.push_back(mm(stringToDouble(m_str))); - break; - case 20: - // control y - sd.controly.push_back(mm(stringToDouble(m_str))); - break; - case 30: - // control z - sd.controlz.push_back(mm(stringToDouble(m_str))); - break; - case 11: - // fit x - sd.fitx.push_back(mm(stringToDouble(m_str))); + spline.weights.push_back(mm(stringToDouble(m_str))); break; - case 21: - // fit y - sd.fity.push_back(mm(stringToDouble(m_str))); + case 10: case 20: case 30: + HandleVectorCoordCode<10, 20, 30>(n, &spline.controlPoints); break; - case 31: - // fit z - sd.fitz.push_back(mm(stringToDouble(m_str))); + case 11: case 21: case 31: + HandleVectorCoordCode<11, 21, 31>(n, &spline.fitPoints); break; case 42: + spline.knotTolerance = stringToDouble(m_str); + break; case 43: + spline.controlPointTolerance = stringToDouble(m_str); + break; case 44: - // skip the next line + spline.fitTolerance = stringToDouble(m_str); break; default: HandleCommonGroupCode(n); @@ -2288,7 +2246,7 @@ bool CDxfRead::ReadSpline() } ResolveColorIndex(); - OnReadSpline(sd); + OnReadSpline(spline); return false; } diff --git a/src/io_dxf/dxf.h b/src/io_dxf/dxf.h index 369bcc3f..b24f704f 100644 --- a/src/io_dxf/dxf.h +++ b/src/io_dxf/dxf.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -180,7 +179,6 @@ struct Dxf_MTEXT { bool acadHasDefinedHeight = false; double acadDefinedHeight = 0.; - }; struct Dxf_VERTEX { @@ -274,29 +272,43 @@ struct Dxf_SOLID { DxfCoords extrusionDirection = { 0., 0., 1. }; }; -//spline data for reading -struct SplineData -{ - DxfCoords norm; - int degree; - int knots; - int control_points; - int fit_points; - int flag; - std::list starttanx; - std::list starttany; - std::list starttanz; - std::list endtanx; - std::list endtany; - std::list endtanz; - std::list knot; - std::list weight; - std::list controlx; - std::list controly; - std::list controlz; - std::list fitx; - std::list fity; - std::list fitz; +struct Dxf_SPLINE { + enum Flag { + None = 0, + Closed = 1, + Periodic = 2, + Rational = 4, + Planar = 8, + Linear = 16 // Planar bit is also set + }; + using Flags = unsigned; + + // Code: 210, 220, 230 + DxfCoords normalVector = { 0., 0., 1. }; + // Code: 70 + Flags flags = Flag::None; + // Code: 71 + int degree = 0; + + // Code: 42 + double knotTolerance = 0.0000001; + // Code: 43 + double controlPointTolerance = 0.0000001; + // Code: 44 + double fitTolerance = 0.0000000001; + + // Code: 12, 22, 32 + std::vector startTangents; + // Code: 13, 23, 33 + std::vector endTangents; + // Code: 40 + std::vector knots; + // Code: 41 + std::vector weights; + // Code: 10, 20, 30 + std::vector controlPoints; + // Code: 11, 21, 31 + std::vector fitPoints; }; //*************************** @@ -634,6 +646,15 @@ class CDxfRead } } + template + void HandleVectorCoordCode(int n, std::vector* ptrVecCoords) + { + if (n == XCode || ptrVecCoords->empty()) + ptrVecCoords->push_back({}); + + HandleCoordCode(n, &ptrVecCoords->back()); + } + void HandleCommonGroupCode(int n); void put_line(const std::string& value); @@ -694,7 +715,7 @@ class CDxfRead bool dir ) = 0; - virtual void OnReadSpline(struct SplineData& sd) = 0; + virtual void OnReadSpline(const Dxf_SPLINE& spline) = 0; virtual void OnReadInsert(const Dxf_INSERT& ins) = 0; diff --git a/src/io_dxf/io_dxf.cpp b/src/io_dxf/io_dxf.cpp index 2bc42bfa..2ad9702f 100644 --- a/src/io_dxf/io_dxf.cpp +++ b/src/io_dxf/io_dxf.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -44,11 +45,14 @@ #include #include +#include +#include +#include #include #include #include -#include +#define MAYO_IO_DXF_DEBUG_TRACE 1 namespace Mayo { namespace IO { @@ -122,7 +126,7 @@ class DxfReader::Internal : public CDxfRead { void OnReadArc(const DxfCoords& s, const DxfCoords& e, const DxfCoords& c, bool dir, bool hidden) override; void OnReadCircle(const DxfCoords& s, const DxfCoords& c, bool dir, bool hidden) override; void OnReadEllipse(const DxfCoords& c, double major_radius, double minor_radius, double rotation, double start_angle, double end_angle, bool dir) override; - void OnReadSpline(struct SplineData& sd) override; + void OnReadSpline(const Dxf_SPLINE& spline) override; void OnReadInsert(const Dxf_INSERT& ins) override; void OnReadDimension(const DxfCoords& s, const DxfCoords& e, const DxfCoords& point, double rotation) override; void OnReadSolid(const Dxf_SOLID& solid) override; @@ -130,8 +134,8 @@ class DxfReader::Internal : public CDxfRead { void ReportError(const std::string& msg) override; void AddGraphics() const override; - static Handle_Geom_BSplineCurve createSplineFromPolesAndKnots(struct SplineData& sd); - static Handle_Geom_BSplineCurve createInterpolationSpline(struct SplineData& sd); + static Handle_Geom_BSplineCurve createSplineFromPolesAndKnots(const Dxf_SPLINE& spline); + static Handle_Geom_BSplineCurve createInterpolationSpline(const Dxf_SPLINE& spline); gp_Pnt toPnt(const DxfCoords& coords) const; void addShape(const TopoDS_Shape& shape); @@ -663,27 +667,29 @@ void DxfReader::Internal::OnReadEllipse( } // Excerpted from FreeCad/src/Mod/Import/App/ImpExpDxf -void DxfReader::Internal::OnReadSpline(SplineData& sd) +void DxfReader::Internal::OnReadSpline(const Dxf_SPLINE& spline) { // https://documentation.help/AutoCAD-DXF/WS1a9193826455f5ff18cb41610ec0a2e719-79e1.htm - // Flags: - // 1: Closed, 2: Periodic, 4: Rational, 8: Planar, 16: Linear - try { Handle_Geom_BSplineCurve geom; - if (sd.control_points > 0) - geom = createSplineFromPolesAndKnots(sd); - else if (sd.fit_points > 0) - geom = createInterpolationSpline(sd); + if (!spline.controlPoints.empty()) + geom = createSplineFromPolesAndKnots(spline); + else if (!spline.fitPoints.empty()) + geom = createInterpolationSpline(spline); if (geom.IsNull()) - throw Standard_Failure(); + throw Standard_Failure("Geom_BSplineCurve object is null"); const TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(geom); this->addShape(edge); } - catch (const Standard_Failure&) { - m_messenger->emitWarning("DxfReader - Failed to create bspline"); + catch (const Standard_Failure& err) { +#ifdef MAYO_IO_DXF_DEBUG_TRACE + std::cout << "ERROR DxfReader::OnReadSpline() -- " << err.GetMessageString() << std::endl; +#endif + m_messenger->emitWarning( + fmt::format("DxfReader - Failed to create bspline({})", err.GetMessageString()) + ); } } @@ -829,86 +835,83 @@ void DxfReader::Internal::addShape(const TopoDS_Shape& shape) } // Excerpted from FreeCad/src/Mod/Import/App/ImpExpDxf -Handle_Geom_BSplineCurve DxfReader::Internal::createSplineFromPolesAndKnots(struct SplineData& sd) +Handle_Geom_BSplineCurve DxfReader::Internal::createSplineFromPolesAndKnots(const Dxf_SPLINE& spline) { - const size_t numPoles = sd.control_points; - if (sd.controlx.size() > numPoles - || sd.controly.size() > numPoles - || sd.controlz.size() > numPoles - || sd.weight.size() > numPoles) - { + if (spline.weights.size() > spline.controlPoints.size()) return {}; - } - - // handle the poles - TColgp_Array1OfPnt occpoles(1, sd.control_points); - int index = 1; - for (auto x : sd.controlx) - occpoles(index++).SetX(x); - - index = 1; - for (auto y : sd.controly) - occpoles(index++).SetY(y); - - index = 1; - for (auto z : sd.controlz) - occpoles(index++).SetZ(z); - - // handle knots and mults - std::set unique; - unique.insert(sd.knot.begin(), sd.knot.end()); - const int numKnots = int(unique.size()); - TColStd_Array1OfInteger occmults(1, numKnots); - TColStd_Array1OfReal occknots(1, numKnots); - index = 1; - for (auto k : unique) { - const auto m = CppUtils::safeStaticCast(std::count(sd.knot.begin(), sd.knot.end(), k)); - occknots(index) = k; - occmults(index) = m; - index++; - } - - // handle weights - TColStd_Array1OfReal occweights(1, sd.control_points); - if (sd.weight.size() == size_t(sd.control_points)) { - index = 1; - for (auto w : sd.weight) - occweights(index++) = w; - } - else { - // non-rational - for (int i = occweights.Lower(); i <= occweights.Upper(); i++) - occweights(i) = 1.0; - } + const bool isPeriodic = (spline.flags & Dxf_SPLINE::Periodic) != 0; + + // Handle poles + const auto iNumPoles = CppUtils::safeStaticCast(spline.controlPoints.size()); + TColgp_Array1OfPnt occPoles(1, iNumPoles); + for (const DxfCoords& pnt : spline.controlPoints) { + const auto iPnt = CppUtils::safeStaticCast(&pnt - &spline.controlPoints.front()); + occPoles.ChangeValue(iPnt + 1) = gp_Pnt{pnt.x, pnt.y, pnt.z}; + } + + // Handle knots and mults + const auto iNumKnots = CppUtils::safeStaticCast(spline.knots.size()); + TColStd_Array1OfReal occKnots(1, iNumKnots); + std::copy(spline.knots.cbegin(), spline.knots.cend(), occKnots.begin()); + + const auto iNumUniqueKnots = BSplCLib::KnotsLength(occKnots, isPeriodic); + TColStd_Array1OfReal occUniqueKnots(1, iNumUniqueKnots); + TColStd_Array1OfInteger occMults(1, iNumUniqueKnots); + BSplCLib::Knots(occKnots, std::ref(occUniqueKnots), std::ref(occMults), isPeriodic); + + // Handle weights + TColStd_Array1OfReal occWeights(1, iNumPoles); + if (spline.weights.size() == spline.controlPoints.size()) + std::copy(spline.weights.cbegin(), spline.weights.cend(), occWeights.begin()); + else + std::fill(occWeights.begin(), occWeights.end(), 1.); // Non-rational + +#ifdef MAYO_IO_DXF_DEBUG_TRACE + // Debug traces + std::cout << std::endl << "createSplineFromPolesAndKnots()"; + std::cout << "\n degree: " << spline.degree; + std::cout << "\n flags: " << spline.flags; + std::cout << "\n isPeriodic: " << isPeriodic; + std::cout << "\n numPoles: " << iNumPoles; + + std::cout << "\n numKnots: " << iNumKnots; + std::cout << "\n occKnots: "; + for (double uknot : occKnots) + std::cout << uknot << ", "; + + std::cout << "\n numUniqueKnots: " << iNumUniqueKnots; + std::cout << "\n occUniqueKnots: "; + for (double uknot : occUniqueKnots) + std::cout << uknot << ", "; + + std::cout << "\n occMults: "; + for (int mult : occMults) + std::cout << mult << ", "; + + std::cout << "\n numStartTangents: " << spline.startTangents.size(); + std::cout << "\n numEndTangents: " << spline.endTangents.size(); + std::cout << "\n BSplCLib::NbPoles(): " << BSplCLib::NbPoles(spline.degree, isPeriodic, occMults); + std::cout << std::endl; +#endif - const bool periodic = sd.flag == 2; - return new Geom_BSplineCurve(occpoles, occweights, occknots, occmults, sd.degree, periodic); + return new Geom_BSplineCurve(occPoles, occWeights, occUniqueKnots, occMults, spline.degree, isPeriodic); } // Excerpted from FreeCad/src/Mod/Import/App/ImpExpDxf -Handle_Geom_BSplineCurve DxfReader::Internal::createInterpolationSpline(struct SplineData& sd) +Handle_Geom_BSplineCurve DxfReader::Internal::createInterpolationSpline(const Dxf_SPLINE& spline) { - const size_t numPoints = sd.fit_points; - if (sd.fitx.size() > numPoints || sd.fity.size() > numPoints || sd.fitz.size() > numPoints) - return {}; - - // handle the poles - Handle_TColgp_HArray1OfPnt fitpoints = new TColgp_HArray1OfPnt(1, sd.fit_points); - int index = 1; - for (auto x : sd.fitx) - fitpoints->ChangeValue(index++).SetX(x); + const auto iNumPoints = CppUtils::safeStaticCast(spline.fitPoints.size()); - index = 1; - for (auto y : sd.fity) - fitpoints->ChangeValue(index++).SetY(y); - - index = 1; - for (auto z : sd.fitz) - fitpoints->ChangeValue(index++).SetZ(z); + // Handle poles + Handle_TColgp_HArray1OfPnt fitpoints = new TColgp_HArray1OfPnt(1, iNumPoints); + for (const DxfCoords& pnt : spline.fitPoints) { + const auto iPnt = CppUtils::safeStaticCast(&pnt - &spline.fitPoints.front()); + fitpoints->ChangeValue(iPnt + 1) = gp_Pnt{pnt.x, pnt.y, pnt.z}; + } - const bool periodic = sd.flag == 2; - GeomAPI_Interpolate interp(fitpoints, periodic, Precision::Confusion()); + const bool isPeriodic = (spline.flags & Dxf_SPLINE::Periodic) != 0; + GeomAPI_Interpolate interp(fitpoints, isPeriodic, Precision::Confusion()); interp.Perform(); return interp.Curve(); } From 812764769f94ebed25aba94279072096ee3d18dc Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Tue, 9 Jan 2024 16:43:23 +0100 Subject: [PATCH 002/101] IO_DXF: rename Dxf_POLYLINE::extrusionDir to extrusionDirection --- src/io_dxf/dxf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io_dxf/dxf.h b/src/io_dxf/dxf.h index b24f704f..303a5117 100644 --- a/src/io_dxf/dxf.h +++ b/src/io_dxf/dxf.h @@ -231,7 +231,7 @@ struct Dxf_POLYLINE { int polygonMeshNVertexCount = 0; double smoothSurfaceMDensity = 0.; double smoothSurfaceNDensity = 0.; - double extrusionDir[3] = { 0., 0., 1. }; + DxfCoords extrusionDirection = { 0., 0., 1. }; std::vector vertices; }; From 74e4e007a05a501cfd1b58ae6bea84f3ed0ff3d4 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Tue, 9 Jan 2024 17:02:28 +0100 Subject: [PATCH 003/101] CI: specify sha256 checks for downloaded files on Windows build --- .github/workflows/ci_windows.yml | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci_windows.yml b/.github/workflows/ci_windows.yml index de894de8..670f92d7 100644 --- a/.github/workflows/ci_windows.yml +++ b/.github/workflows/ci_windows.yml @@ -6,7 +6,17 @@ jobs: build-windows-msvc: strategy: matrix: - occ_version: [7.3.0, 7.4.0, 7.5.0, 7.6.0, 7.7.0] + include: + - occ_version: 7.3.0 + occ_zip_sha256: '2503e69d595bb4c4e760b629aba3d8cee5e6b8d0da43b0d790a7949c67fd15d0' + - occ_version: 7.4.0 + occ_zip_sha256: '6664b9a4213036ff0960edd2426258cf66844386c1a2338fa62e008f40c476bb' + - occ_version: 7.5.0 + occ_zip_sha256: '8000f261ecbf6689e48439aa5895304069d00a14efe28eb7f1d108450cb7a74f' + - occ_version: 7.6.0 + occ_zip_sha256: '137788c7693f74fb98797adfd06c1b00f66dac59beca5c3073c1f62c98b7a80f' + - occ_version: 7.7.0 + occ_zip_sha256: '82afb7bd5fd7097189ee4e98bf3bc02b4030cae7a82967db68d5f2b556801779' runs-on: windows-latest @@ -32,11 +42,10 @@ jobs: uses: carlosperate/download-file-action@v2 with: file-url: 'https://www.fougue.pro/share/bin/OpenCASCADE-${{matrix.occ_version}}-vc14-64.zip' + sha256: ${{matrix.occ_zip_sha256}} - name: Extract OpenCascade - shell: pwsh - run: | - Expand-Archive -Path OpenCASCADE-${{matrix.occ_version}}-vc14-64.zip -DestinationPath . + run: 7z x OpenCASCADE-${{matrix.occ_version}}-vc14-64.zip - name: Cache Assimp archive id: cache-assimp @@ -50,16 +59,16 @@ jobs: uses: carlosperate/download-file-action@v2 with: file-url: 'https://www.fougue.pro/share/bin/assimp-5.3.1.zip' + sha256: 'b6097488fa5b7a0cc610e8258e74aaa9552b48a13bc8680eedd66512d788e64f' - name: Extract Assimp - shell: pwsh - run: | - Expand-Archive -Path assimp-5.3.1.zip -DestinationPath . + run: 7z x assimp-5.3.1.zip - name: Download jom.exe uses: carlosperate/download-file-action@v2 with: file-url: 'https://www.fougue.pro/share/bin/jom.exe' + sha256: '700943516856c15675212bbcecc4e3d7580514456f8b6f09d634e98e25296ad4' - name: Get count of CPU cores uses: SimenB/github-actions-cpu-cores@v1 @@ -86,8 +95,7 @@ jobs: - name: Build working-directory: ${{github.workspace}}/build - run: | - ..\jom.exe -j${{steps.cpu-cores.outputs.count}} + run: ..\jom.exe -j${{steps.cpu-cores.outputs.count}} - name: Execute Unit Tests working-directory: ${{github.workspace}}/build From d1b3545e9257b3bba26b3c2805b510af5d163048 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Wed, 10 Jan 2024 16:57:08 +0100 Subject: [PATCH 004/101] Port to OpenCascade 7.8.0 --- .github/workflows/ci_windows.yml | 2 ++ mayo.pro | 36 +++++++++++++++++++++++--------- src/app/widget_main_control.cpp | 1 - src/base/brep_utils.cpp | 8 +++++-- src/base/brep_utils.h | 2 +- src/base/caf_utils.h | 4 +++- src/base/tkernel_utils.h | 2 ++ tests/test_base.cpp | 5 +++-- 8 files changed, 43 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci_windows.yml b/.github/workflows/ci_windows.yml index 670f92d7..4b4306b4 100644 --- a/.github/workflows/ci_windows.yml +++ b/.github/workflows/ci_windows.yml @@ -17,6 +17,8 @@ jobs: occ_zip_sha256: '137788c7693f74fb98797adfd06c1b00f66dac59beca5c3073c1f62c98b7a80f' - occ_version: 7.7.0 occ_zip_sha256: '82afb7bd5fd7097189ee4e98bf3bc02b4030cae7a82967db68d5f2b556801779' + - occ_version: 7.8.0 + occ_zip_sha256: 'beb30920efe5e5384d500d43dcb8ba1f476a76bb65b268b1af32d807b697bdee' runs-on: windows-latest diff --git a/mayo.pro b/mayo.pro index a21b22d9..f2bd14ed 100644 --- a/mayo.pro +++ b/mayo.pro @@ -173,19 +173,36 @@ LIBS += \ -lTKXmlXCAF \ -lTKXSBase \ -versionAtLeast(OCC_VERSION_STR, 7.7.0) { - LIBS += -lTKXDE +versionAtLeast(OCC_VERSION_STR, 7.8.0) { + # -- IGES support + LIBS += -lTKDEIGES + # -- STEP support + LIBS += -lTKDESTEP + # -- STL support + LIBS += -lTKDESTL + # -- VRML support + LIBS += -lTKDEVRML +} else { + # -- IGES support + LIBS += -lTKIGES -lTKXDEIGES + # -- STEP support + LIBS += -lTKSTEP -lTKSTEP209 -lTKSTEPAttr -lTKSTEPBase -lTKXDESTEP + # -- STL support + LIBS += -lTKSTL + # -- VRML support + LIBS += -lTKVRML + + versionAtLeast(OCC_VERSION_STR, 7.7.0) { + LIBS += -lTKXDE + } } -# -- IGES support -LIBS += -lTKIGES -lTKXDEIGES -# -- STEP support -LIBS += -lTKSTEP -lTKSTEP209 -lTKSTEPAttr -lTKSTEPBase -lTKXDESTEP -# -- STL support -LIBS += -lTKSTL # -- OBJ/glTF support versionAtLeast(OCC_VERSION_STR, 7.4.0) { LIBS += -lTKRWMesh + versionAtLeast(OCC_VERSION_STR, 7.8.0) { + LIBS += -lTKDEOBJ -lTKDEGLTF + } } else { SOURCES -= \ src/io_occ/io_occ_base_mesh.cpp \ @@ -205,8 +222,7 @@ versionAtLeast(OCC_VERSION_STR, 7.4.0) { SOURCES -= src/io_occ/io_occ_obj_writer.cpp message(OBJ writer disabled because OpenCascade < v7.6) } -# -- VRML support -LIBS += -lTKVRML + !versionAtLeast(OCC_VERSION_STR, 7.7.0) { SOURCES -= src/io_occ/io_occ_vrml_reader.cpp message(VRML reader disabled because OpenCascade < v7.7) diff --git a/src/app/widget_main_control.cpp b/src/app/widget_main_control.cpp index 51eca635..3240e3ae 100644 --- a/src/app/widget_main_control.cpp +++ b/src/app/widget_main_control.cpp @@ -312,7 +312,6 @@ QWidget* WidgetMainControl::recreateLeftHeaderPlaceHolder() WidgetGuiDocument* WidgetMainControl::widgetGuiDocument(int idx) const { - assert(idx == -1 || (0 <= idx && idx < m_ui->stack_GuiDocuments->count())); return qobject_cast(m_ui->stack_GuiDocuments->widget(idx)); } diff --git a/src/base/brep_utils.cpp b/src/base/brep_utils.cpp index 1b2f1197..b694f5bc 100644 --- a/src/base/brep_utils.cpp +++ b/src/base/brep_utils.cpp @@ -59,9 +59,13 @@ bool BRepUtils::moreComplex(TopAbs_ShapeEnum lhs, TopAbs_ShapeEnum rhs) return lhs < rhs; } -int BRepUtils::hashCode(const TopoDS_Shape& shape) +size_t BRepUtils::hashCode(const TopoDS_Shape& shape) { - return !shape.IsNull() ? shape.HashCode(INT_MAX) : -1; +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 8, 0) + return std::hash{}(shape); +#else + return shape.HashCode(INT_MAX); +#endif } std::string BRepUtils::shapeToString(const TopoDS_Shape& shape) diff --git a/src/base/brep_utils.h b/src/base/brep_utils.h index 76830e89..a98dd2e1 100644 --- a/src/base/brep_utils.h +++ b/src/base/brep_utils.h @@ -53,7 +53,7 @@ struct BRepUtils { // Returns hash code computed from 'shape' // Computation uses the internal TShape and Location, but Orientation is not considered // Returned hash code is in the range [1, max(int)] - static int hashCode(const TopoDS_Shape& shape); + static size_t hashCode(const TopoDS_Shape& shape); // Serializes 'shape' into a string representation static std::string shapeToString(const TopoDS_Shape& shape); diff --git a/src/base/caf_utils.h b/src/base/caf_utils.h index d849de53..8863aa59 100644 --- a/src/base/caf_utils.h +++ b/src/base/caf_utils.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -43,7 +44,7 @@ struct CafUtils { } // namespace Mayo - +#if OCC_VERSION_HEX < 0x070800 #include namespace std { @@ -55,6 +56,7 @@ template<> struct hash { }; } // namespace std +#endif // -- // -- Implementation diff --git a/src/base/tkernel_utils.h b/src/base/tkernel_utils.h index 3dd45009..4f1d97af 100644 --- a/src/base/tkernel_utils.h +++ b/src/base/tkernel_utils.h @@ -55,6 +55,7 @@ class TKernelUtils { } // namespace Mayo +#if OCC_VERSION_HEX < OCC_VERSION_CHECK(7, 8, 0) namespace std { // Specialization of C++11 std::hash<> functor for opencascade::handle<> objects @@ -65,3 +66,4 @@ template struct hash> { }; } // namespace std +#endif diff --git a/tests/test_base.cpp b/tests/test_base.cpp index e98f482f..b28040e5 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -653,9 +653,10 @@ void TestBase::BRepUtils_test() const TopoDS_Shape shapeNull; const TopoDS_Shape shapeBase = BRepPrimAPI_MakeBox(25, 25, 25); const TopoDS_Shape shapeCopy = shapeBase; - QCOMPARE(BRepUtils::hashCode(shapeNull), -1); - QVERIFY(BRepUtils::hashCode(shapeBase) >= 0); + const TopoDS_Shape shapeOther = BRepPrimAPI_MakeBox(40, 40, 40); + QCOMPARE(BRepUtils::hashCode(shapeNull), BRepUtils::hashCode(TopoDS_Shape{})); QCOMPARE(BRepUtils::hashCode(shapeBase), BRepUtils::hashCode(shapeCopy)); + QVERIFY(BRepUtils::hashCode(shapeBase) != BRepUtils::hashCode(shapeOther)); } } From f23aff6228b06f00164be0a7a1b138e35792aa90 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 12 Jan 2024 12:57:49 +0100 Subject: [PATCH 005/101] Set dev version to 0.9 [skip ci] --- version.pri | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.pri b/version.pri index 07bcd9d9..83a6fb1e 100644 --- a/version.pri +++ b/version.pri @@ -9,7 +9,7 @@ defined(HAVE_GIT, var) { } MAYO_VERSION_MAJ = 0 -MAYO_VERSION_MIN = 8 +MAYO_VERSION_MIN = 9 MAYO_VERSION_PAT = 0 VERSION = $${MAYO_VERSION_MAJ}.$${MAYO_VERSION_MIN}.$${MAYO_VERSION_PAT} MAYO_VERSION = $${VERSION} From 2c28702a0ac7f15d48c52e5ba34cd21b0d97f3be Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 18 Jan 2024 10:37:13 +0100 Subject: [PATCH 006/101] DXF: support 3D meshes with POLYLINE and 3DFACE Relates to GitHub #251 --- src/io_dxf/dxf.cpp | 92 ++++++++++++++++++++++++++++- src/io_dxf/dxf.h | 56 +++++++++++++++++- src/io_dxf/io_dxf.cpp | 134 ++++++++++++++++++++++++++++++------------ 3 files changed, 240 insertions(+), 42 deletions(-) diff --git a/src/io_dxf/dxf.cpp b/src/io_dxf/dxf.cpp index 03ac930c..d65ed947 100644 --- a/src/io_dxf/dxf.cpp +++ b/src/io_dxf/dxf.cpp @@ -2791,8 +2791,13 @@ bool CDxfRead::ReadVertex(Dxf_VERTEX* vertex) y_found = y_found || n == 20; HandleCoordCode(n, &vertex->point); break; + case 40: + vertex->startingWidth = stringToDouble(m_str); + break; + case 41: + vertex->endingWidth = stringToDouble(m_str); + break; case 42: { - // bulge const int bulge = stringToInt(m_str); if (bulge == 0) vertex->bulge = Dxf_VERTEX::Bulge::StraightSegment; @@ -2801,9 +2806,63 @@ bool CDxfRead::ReadVertex(Dxf_VERTEX* vertex) } break; case 70: - // flags vertex->flags = stringToUnsigned(m_str); break; + case 71: + vertex->polyfaceMeshVertex1 = stringToInt(m_str); + break; + case 72: + vertex->polyfaceMeshVertex2 = stringToInt(m_str); + break; + case 73: + vertex->polyfaceMeshVertex3 = stringToInt(m_str); + break; + case 74: + vertex->polyfaceMeshVertex4 = stringToInt(m_str); + break; + default: + HandleCommonGroupCode(n); + break; + } + } + + return false; +} + +bool CDxfRead::Read3dFace() +{ + Dxf_3DFACE face; + while (!m_ifs.eof()) { + get_line(); + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + ResolveColorIndex(); + OnRead3dFace(face); + return true; + } + else if (isStringToErrorValue(n)) { + this->ReportError_readInteger("DXF::Read3dFace()"); + return false; + } + + get_line(); + switch (n) { + case 10: case 20: case 30: + HandleCoordCode<10, 20, 30>(n, &face.corner1); + break; + case 11: case 21: case 31: + HandleCoordCode<11, 21, 31>(n, &face.corner2); + break; + case 12: case 22: case 32: + HandleCoordCode<12, 22, 32>(n, &face.corner3); + break; + case 13: case 23: case 33: + HandleCoordCode<13, 23, 33>(n, &face.corner4); + face.hasCorner4 = true; + break; + case 70: + face.flags = stringToUnsigned(m_str); + break; default: HandleCommonGroupCode(n); break; @@ -2913,11 +2972,37 @@ bool CDxfRead::ReadPolyLine() return true; } + break; + case 39: + polyline.thickness = stringToDouble(m_str); break; case 70: - // flags polyline.flags = stringToUnsigned(m_str); break; + case 40: + polyline.defaultStartWidth = stringToDouble(m_str); + break; + case 41: + polyline.defaultEndWidth = stringToDouble(m_str); + break; + case 71: + polyline.polygonMeshMVertexCount = stringToInt(m_str); + break; + case 72: + polyline.polygonMeshNVertexCount = stringToInt(m_str); + break; + case 73: + polyline.smoothSurfaceMDensity = stringToDouble(m_str); + break; + case 74: + polyline.smoothSurfaceNDensity = stringToDouble(m_str); + break; + case 75: + polyline.type = static_cast(stringToUnsigned(m_str)); + break; + case 210: case 220: case 230: + HandleCoordCode<210, 220, 230>(n, &polyline.extrusionDirection); + break; default: HandleCommonGroupCode(n); break; @@ -3402,6 +3487,7 @@ void CDxfRead::DoRead(bool ignore_errors) mapEntityHandler.insert({ "POLYLINE", [=]{ return ReadPolyLine(); } }); mapEntityHandler.insert({ "SECTION", [=]{ return ReadSection(); } }); mapEntityHandler.insert({ "SOLID", [=]{ return ReadSolid(); } }); + mapEntityHandler.insert({ "3DFACE", [=]{ return Read3dFace(); } }); mapEntityHandler.insert({ "SPLINE", [=]{ return ReadSpline(); } }); mapEntityHandler.insert({ "STYLE", [=]{ return ReadStyle(); } }); mapEntityHandler.insert({ "TEXT", [=]{ return ReadText(); } }); diff --git a/src/io_dxf/dxf.h b/src/io_dxf/dxf.h index 303a5117..8c52ab02 100644 --- a/src/io_dxf/dxf.h +++ b/src/io_dxf/dxf.h @@ -192,13 +192,32 @@ struct Dxf_VERTEX { SplineFrameControlPoint = 16, Polyline3dVertex = 32, Polygon3dVertex = 64, - PolyfaceMesgVertex = 128 + PolyfaceMeshVertex = 128 }; using Flags = unsigned; + // Code: 10, 20, 30 DxfCoords point = {}; + // Code: 40 + double startingWidth = 0.; + // Code: 41 + double endingWidth = 0.; + // Code: 42 Bulge bulge = Bulge::StraightSegment; + // Code: 70 Flags flags = Flag::None; + // Code: 50 + double curveFitTangentDirection = 0.; + // Code: 71 + int polyfaceMeshVertex1 = 0; + // Code: 72 + int polyfaceMeshVertex2 = 0; + // Code: 73 + int polyfaceMeshVertex3 = 0; + // Code: 74 + int polyfaceMeshVertex4 = 0; + // Code: 91 + // int identifier = 0; }; struct Dxf_POLYLINE { @@ -222,16 +241,27 @@ struct Dxf_POLYLINE { BezierSurface = 8 }; - double elevation = 0.; + // Code: 39 double thickness = 0.; + // Code: 70 Flags flags = Flag::None; + // Code: 40 double defaultStartWidth = 0.; + // Code: 41 double defaultEndWidth = 0.; + // Code: 71(number of vertices in the mesh) int polygonMeshMVertexCount = 0; + // Code: 72(number of faces in the mesh) int polygonMeshNVertexCount = 0; + // Code: 73 double smoothSurfaceMDensity = 0.; + // Code: 74 double smoothSurfaceNDensity = 0.; + // Code: 75 + Type type = Type::NoSmoothSurfaceFitted; + // Code: 210, 220, 230 DxfCoords extrusionDirection = { 0., 0., 1. }; + std::vector vertices; }; @@ -256,7 +286,7 @@ struct Dxf_INSERT { DxfCoords extrusionDirection = { 0., 0., 1. }; }; -struct Dxf_SOLID { +struct Dxf_QuadBase { // Code: 10, 20, 30 DxfCoords corner1; // Code: 11, 21, 31 @@ -266,6 +296,23 @@ struct Dxf_SOLID { // Code: 13, 23, 33 DxfCoords corner4; bool hasCorner4 = false; +}; + +struct Dxf_3DFACE : public Dxf_QuadBase { + enum Flag { + None = 0, + InvisibleEdge1 = 1, + InvisibleEdge2 = 2, + InvisibleEdge3 = 4, + InvisibleEdge4 = 8 + }; + using Flags = unsigned; + + // Code: 70 + Flags flags = Flag::None; +}; + +struct Dxf_SOLID : public Dxf_QuadBase { // Code: 39 double thickness = 0.; // Code: 210, 220, 230 @@ -603,6 +650,7 @@ class CDxfRead bool ReadLwPolyLine(); bool ReadPolyLine(); bool ReadVertex(Dxf_VERTEX* vertex); + bool Read3dFace(); bool ReadSolid(); bool ReadSection(); bool ReadTable(); @@ -693,6 +741,8 @@ class CDxfRead virtual void OnReadPolyline(const Dxf_POLYLINE&) = 0; + virtual void OnRead3dFace(const Dxf_3DFACE&) = 0; + virtual void OnReadPoint(const DxfCoords& s) = 0; virtual void OnReadText(const Dxf_TEXT&) = 0; diff --git a/src/io_dxf/io_dxf.cpp b/src/io_dxf/io_dxf.cpp index 2ad9702f..b01f5e69 100644 --- a/src/io_dxf/io_dxf.cpp +++ b/src/io_dxf/io_dxf.cpp @@ -13,6 +13,7 @@ #include "../base/math_utils.h" #include "../base/mesh_utils.h" #include "../base/messenger.h" +#include "../base/occ_handle.h" #include "../base/property_builtins.h" #include "../base/property_enumeration.h" #include "../base/task_progress.h" @@ -22,27 +23,28 @@ #include "aci_table.h" #include "dxf.h" -#include -#include -#include -#include #include #include #include #include #include +#include #include -#include -#include #include #include #include #include +#include +#include +#include #include #include #include #include #include +#include +#include +#include #include #include @@ -130,6 +132,7 @@ class DxfReader::Internal : public CDxfRead { void OnReadInsert(const Dxf_INSERT& ins) override; void OnReadDimension(const DxfCoords& s, const DxfCoords& e, const DxfCoords& point, double rotation) override; void OnReadSolid(const Dxf_SOLID& solid) override; + void OnRead3dFace(const Dxf_3DFACE& face) override; void ReportError(const std::string& msg) override; void AddGraphics() const override; @@ -139,6 +142,8 @@ class DxfReader::Internal : public CDxfRead { gp_Pnt toPnt(const DxfCoords& coords) const; void addShape(const TopoDS_Shape& shape); + + TopoDS_Face makeFace(const Dxf_QuadBase& quad) const; }; class DxfReader::Properties : public PropertyGroup { @@ -419,17 +424,45 @@ void DxfReader::Internal::OnReadLine(const DxfCoords& s, const DxfCoords& e, boo void DxfReader::Internal::OnReadPolyline(const Dxf_POLYLINE& polyline) { const auto& vertices = polyline.vertices; - const bool isPolylineClosed = polyline.flags & Dxf_POLYLINE::Flag::Closed; - const int nodeCount = CppUtils::safeStaticCast(vertices.size() + (isPolylineClosed ? 1 : 0)); - MeshUtils::Polygon3dBuilder polygonBuilder(nodeCount); - for (unsigned i = 0; i < vertices.size(); ++i) - polygonBuilder.setNode(i + 1, this->toPnt(vertices.at(i).point)); + if (polyline.flags & Dxf_POLYLINE::Flag::PolyfaceMesh) { + const int meshVertexCount = polyline.polygonMeshMVertexCount; + TColgp_Array1OfPnt nodes(1, meshVertexCount); + for (int i = 0; i < meshVertexCount; ++i) + nodes.ChangeValue(i + 1) = this->toPnt(vertices.at(i).point); + + const int meshFaceCount = polyline.polygonMeshNVertexCount; + std::vector vecTriangle; + vecTriangle.reserve(meshFaceCount); + for (int i = 0; i < meshFaceCount; ++i) { + const Dxf_VERTEX& face = vertices.at(meshVertexCount + i); + const auto meshVertex1 = std::abs(face.polyfaceMeshVertex1); + const auto meshVertex2 = std::abs(face.polyfaceMeshVertex2); + const auto meshVertex3 = std::abs(face.polyfaceMeshVertex3); + const auto meshVertex4 = std::abs(face.polyfaceMeshVertex4); + vecTriangle.emplace_back(meshVertex1, meshVertex2, meshVertex3); + if (meshVertex4 != 0 && meshVertex3 != meshVertex4) + vecTriangle.emplace_back(meshVertex1, meshVertex3, meshVertex4); + } - if (isPolylineClosed) - polygonBuilder.setNode(nodeCount, this->toPnt(vertices.at(0).point)); + Poly_Array1OfTriangle triangles(1, static_cast(vecTriangle.size())); + for (unsigned i = 0; i < vecTriangle.size(); ++i) + triangles.ChangeValue(i + 1) = vecTriangle.at(i); + + this->addShape(BRepUtils::makeFace(new Poly_Triangulation(nodes, triangles))); + } + else { + const bool isPolylineClosed = polyline.flags & Dxf_POLYLINE::Flag::Closed; + const int nodeCount = CppUtils::safeStaticCast(vertices.size() + (isPolylineClosed ? 1 : 0)); + MeshUtils::Polygon3dBuilder polygonBuilder(nodeCount); + for (unsigned i = 0; i < vertices.size(); ++i) + polygonBuilder.setNode(i + 1, this->toPnt(vertices.at(i).point)); - polygonBuilder.finalize(); - this->addShape(BRepUtils::makeEdge(polygonBuilder.get())); + if (isPolylineClosed) + polygonBuilder.setNode(nodeCount, this->toPnt(vertices.at(0).point)); + + polygonBuilder.finalize(); + this->addShape(BRepUtils::makeEdge(polygonBuilder.get())); + } } void DxfReader::Internal::OnReadPoint(const DxfCoords& s) @@ -767,32 +800,32 @@ void DxfReader::Internal::OnReadDimension(const DxfCoords& s, const DxfCoords& e void DxfReader::Internal::OnReadSolid(const Dxf_SOLID& solid) { - const gp_Pnt p1 = this->toPnt(solid.corner1); - const gp_Pnt p2 = this->toPnt(solid.corner2); - const gp_Pnt p3 = this->toPnt(solid.corner3); - const gp_Pnt p4 = this->toPnt(solid.corner4); + Dxf_QuadBase quad = solid; + if (solid.hasCorner4) { + // See https://ezdxf.readthedocs.io/en/stable/dxfentities/solid.html + std::swap(quad.corner3, quad.corner4); + } - TopoDS_Face face; try { - BRepBuilderAPI_MakeWire makeWire; - makeWire.Add(BRepBuilderAPI_MakeEdge(p1, p2)); - if (solid.hasCorner4 && !p3.IsEqual(p4, Precision::Confusion())) { - makeWire.Add(BRepBuilderAPI_MakeEdge(p2, p4)); - makeWire.Add(BRepBuilderAPI_MakeEdge(p4, p3)); - } - else { - makeWire.Add(BRepBuilderAPI_MakeEdge(p2, p3)); - } - - makeWire.Add(BRepBuilderAPI_MakeEdge(p3, p1)); - if (makeWire.IsDone()) - face = BRepBuilderAPI_MakeFace(makeWire.Wire(), true/*onlyPlane*/); - } catch (...) { + const TopoDS_Face face = makeFace(quad); + if (!face.IsNull()) + this->addShape(face); + } + catch (...) { m_messenger->emitError("OnReadSolid() failed"); } +} - if (!face.IsNull()) - this->addShape(face); +void DxfReader::Internal::OnRead3dFace(const Dxf_3DFACE& face) +{ + try { + const TopoDS_Face brepFace = makeFace(face); + if (!brepFace.IsNull()) + this->addShape(brepFace); + } + catch (...) { + m_messenger->emitError("OnReadFace() failed"); + } } void DxfReader::Internal::ReportError(const std::string& msg) @@ -834,6 +867,35 @@ void DxfReader::Internal::addShape(const TopoDS_Shape& shape) } } +TopoDS_Face DxfReader::Internal::makeFace(const Dxf_QuadBase& quad) const +{ + const gp_Pnt p1 = this->toPnt(quad.corner1); + const gp_Pnt p2 = this->toPnt(quad.corner2); + const gp_Pnt p3 = this->toPnt(quad.corner3); + const gp_Pnt p4 = this->toPnt(quad.corner4); + + const double pntTolerance = Precision::Confusion(); + if (p1.IsEqual(p2, pntTolerance) || p1.IsEqual(p3, pntTolerance) || p2.IsEqual(p3, pntTolerance)) + return {}; + + TopoDS_Face face; + BRepBuilderAPI_MakeWire makeWire; + makeWire.Add(BRepBuilderAPI_MakeEdge(p1, p2)); + makeWire.Add(BRepBuilderAPI_MakeEdge(p2, p3)); + if (quad.hasCorner4 && !p3.IsEqual(p4, pntTolerance) && !p1.IsEqual(p4, pntTolerance)) { + makeWire.Add(BRepBuilderAPI_MakeEdge(p3, p4)); + makeWire.Add(BRepBuilderAPI_MakeEdge(p4, p1)); + } + else { + makeWire.Add(BRepBuilderAPI_MakeEdge(p3, p1)); + } + + if (makeWire.IsDone()) + face = BRepBuilderAPI_MakeFace(makeWire.Wire(), true/*onlyPlane*/); + + return face; +} + // Excerpted from FreeCad/src/Mod/Import/App/ImpExpDxf Handle_Geom_BSplineCurve DxfReader::Internal::createSplineFromPolesAndKnots(const Dxf_SPLINE& spline) { From 1966a23a8d81ef3fe2c9015abd1fc3f5da578e60 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 19 Jan 2024 12:41:26 +0100 Subject: [PATCH 007/101] QMake: remove needless defines for Linux --- mayo.pro | 10 ++++++++++ opencascade.pri | 18 +----------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/mayo.pro b/mayo.pro index f2bd14ed..f91049b1 100644 --- a/mayo.pro +++ b/mayo.pro @@ -13,11 +13,21 @@ CONFIG(debug, debug|release) { message(Mayo version $$MAYO_VERSION release) } +# Check Qt version message(Qt version $$QT_VERSION) !versionAtLeast(QT_VERSION, 5.14) { error(Qt >= 5.14 is required but detected version is $$QT_VERSION) } +# Check architecture +equals(QT_ARCH, x86_64) { + # NOP +} else:equals(QT_ARCH, i386) { + # NOP +} else { + warning(Platform architecture may be not supported(QT_ARCH = $$QT_ARCH)) +} + QT += core gui widgets greaterThan(QT_MAJOR_VERSION, 5) { QT += openglwidgets diff --git a/opencascade.pri b/opencascade.pri index 18feb540..26ea5787 100644 --- a/opencascade.pri +++ b/opencascade.pri @@ -18,16 +18,9 @@ isEmpty(CASCADE_DEFINES):CASCADE_DEFINES = $$(CSF_DEFINES) INCLUDEPATH += $$CASCADE_INC_DIR -linux:DEFINES += \ - HAVE_CONFIG_H \ - HAVE_FSTREAM \ - HAVE_IOSTREAM \ - HAVE_IOMANIP \ - HAVE_LIMITS_H - DEFINES += $$split(CASCADE_DEFINES, ;) DEFINES += OCCT_HANDLE_NOCAST -linux:DEFINES += LIN LININTEL OCC_CONVERT_SIGNALS +linux:DEFINES += OCC_CONVERT_SIGNALS # Find OCC version OCC_VERSION_FILE_CONTENTS = $$cat($$CASCADE_INC_DIR/Standard_Version.hxx, lines) @@ -41,14 +34,5 @@ OCC_VERSION_PATCH = $$section(OCC_VERSION_PATCH, " ", -1) OCC_VERSION_STR = $$join($$list($$OCC_VERSION_MAJOR, $$OCC_VERSION_MINOR, $$OCC_VERSION_PATCH), .) -# Platform dependent config -equals(QT_ARCH, i386) { - # NOP -} else:equals(QT_ARCH, x86_64) { - DEFINES += _OCC64 -} else { - warning(Platform architecture may be not supported(QT_ARCH = $$QT_ARCH)) -} - LIBS += $$system_path($$join(CASCADE_LIB_DIR, " -L", -L)) QMAKE_RPATHDIR += $$CASCADE_LIB_DIR From 5abdb928d20f8248f2f44eb95f5026d71d3b905e Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 26 Jan 2024 13:29:00 +0100 Subject: [PATCH 008/101] Measure: add bounding box measurement Relates to GitHub #252 --- src/app/widget_gui_document.cpp | 9 ++- src/app/widget_measure.cpp | 33 ++++++-- src/app/widget_measure.h | 1 + src/app/widget_measure.ui | 128 +++++++++++++++++++++--------- src/base/quantity.h | 2 + src/base/unit.h | 9 +++ src/base/unit_system.cpp | 21 +++++ src/base/unit_system.h | 1 + src/graphics/graphics_scene.cpp | 25 +++++- src/graphics/graphics_scene.h | 8 +- src/measure/measure_display.cpp | 85 ++++++++++++++++++++ src/measure/measure_display.h | 22 +++++ src/measure/measure_tool.cpp | 8 +- src/measure/measure_tool.h | 20 ++++- src/measure/measure_tool_brep.cpp | 47 ++++++++++- src/measure/measure_tool_brep.h | 2 + src/measure/measure_type.h | 11 ++- tests/test_measure.cpp | 22 ++++- tests/test_measure.h | 5 +- 19 files changed, 403 insertions(+), 56 deletions(-) diff --git a/src/app/widget_gui_document.cpp b/src/app/widget_gui_document.cpp index 66bc1c5e..5dde3114 100644 --- a/src/app/widget_gui_document.cpp +++ b/src/app/widget_gui_document.cpp @@ -365,7 +365,8 @@ ButtonFlat* WidgetGuiDocument::createViewBtn(QWidget* parent, Theme::Icon icon, const QColor bkgndColor = m_qtOccView->supportsWidgetOpacity() ? Qt::transparent : - mayoTheme()->color(Theme::Color::ButtonView3d_Background); + mayoTheme()->color(Theme::Color::ButtonView3d_Background) + ; auto btn = new ButtonFlat(parent); btn->setBackgroundBrush(bkgndColor); @@ -476,8 +477,10 @@ void WidgetGuiDocument::layoutViewControls() case Aspect_TOTP_LEFT_LOWER: return { ctrlXOffset, this->height() - viewCubeBndSize - margin - ctrlHeight }; case Aspect_TOTP_RIGHT_LOWER: - return { this->width() - viewCubeBndSize + ctrlXOffset, - this->height() - viewCubeBndSize - margin - ctrlHeight }; + return { + this->width() - viewCubeBndSize + ctrlXOffset, + this->height() - viewCubeBndSize - margin - ctrlHeight + }; default: return { margin, margin }; } // endswitch diff --git a/src/app/widget_measure.cpp b/src/app/widget_measure.cpp index 9a681677..237bb184 100644 --- a/src/app/widget_measure.cpp +++ b/src/app/widget_measure.cpp @@ -91,6 +91,10 @@ WidgetMeasure::WidgetMeasure(GuiDocument* guiDoc, QWidget* parent) m_ui->combo_AreaUnit, qOverload(&QComboBox::currentIndexChanged), this, &WidgetMeasure::onMeasureUnitsChanged ); + QObject::connect( + m_ui->combo_VolumeUnit, qOverload(&QComboBox::currentIndexChanged), + this, &WidgetMeasure::onMeasureUnitsChanged + ); this->onMeasureTypeChanged(m_ui->combo_MeasureType->currentIndex()); this->updateMessagePanel(); @@ -137,6 +141,7 @@ MeasureType WidgetMeasure::toMeasureType(int comboBoxId) case 5: return MeasureType::Angle; case 6: return MeasureType::Length; case 7: return MeasureType::Area; + case 8: return MeasureType::BoundingBox; } return MeasureType::None; } @@ -176,6 +181,21 @@ AreaUnit WidgetMeasure::toMeasureAreaUnit(int comboBoxId) return {}; } +VolumeUnit WidgetMeasure::toMeasureVolumeUnit(int comboBoxId) +{ + switch (comboBoxId) { + case 0: return VolumeUnit::CubicMillimeter; + case 1: return VolumeUnit::CubicCentimeter; + case 2: return VolumeUnit::CubicMeter; + case 3: return VolumeUnit::CubicInch; + case 4: return VolumeUnit::CubicFoot; + case 5: return VolumeUnit::Liter; + case 6: return VolumeUnit::ImperialGallon; + case 7: return VolumeUnit::USGallon; + } + return {}; +} + void WidgetMeasure::onMeasureTypeChanged(int id) { // Update widgets visibility @@ -183,12 +203,15 @@ void WidgetMeasure::onMeasureTypeChanged(int id) const bool measureIsLengthBased = measureType != MeasureType::Angle; const bool measureIsAngle = measureType == MeasureType::Angle; const bool measureIsArea = measureType == MeasureType::Area; + const bool measureIsVolume = measureType == MeasureType::BoundingBox; m_ui->label_LengthUnit->setVisible(measureIsLengthBased && !measureIsArea); - m_ui->combo_LengthUnit->setVisible(measureIsLengthBased && !measureIsArea); + m_ui->combo_LengthUnit->setVisible(m_ui->label_LengthUnit->isVisible()); m_ui->label_AngleUnit->setVisible(measureIsAngle); - m_ui->combo_AngleUnit->setVisible(measureIsAngle); + m_ui->combo_AngleUnit->setVisible(m_ui->label_AngleUnit->isVisible()); m_ui->label_AreaUnit->setVisible(measureIsArea); - m_ui->combo_AreaUnit->setVisible(measureIsArea); + m_ui->combo_AreaUnit->setVisible(m_ui->label_AreaUnit->isVisible()); + m_ui->label_VolumeUnit->setVisible(measureIsVolume); + m_ui->combo_VolumeUnit->setVisible(m_ui->label_VolumeUnit->isVisible()); auto gfxScene = m_guiDoc->graphicsScene(); @@ -239,6 +262,7 @@ MeasureDisplayConfig WidgetMeasure::currentMeasureDisplayConfig() const cfg.lengthUnit = WidgetMeasure::toMeasureLengthUnit(m_ui->combo_LengthUnit->currentIndex()); cfg.angleUnit = WidgetMeasure::toMeasureAngleUnit(m_ui->combo_AngleUnit->currentIndex()); cfg.areaUnit = WidgetMeasure::toMeasureAreaUnit(m_ui->combo_AreaUnit->currentIndex()); + cfg.volumeUnit = WidgetMeasure::toMeasureVolumeUnit(m_ui->combo_VolumeUnit->currentIndex()); cfg.doubleToStringOptions.locale = AppModule::get()->stdLocale(); cfg.doubleToStringOptions.decimalCount = AppModule::get()->defaultTextOptions().unitDecimals; cfg.devicePixelRatio = this->devicePixelRatioF(); @@ -335,8 +359,7 @@ void WidgetMeasure::onGraphicsSelectionChanged() measure->update(measureDisplayConfig); measure->adaptGraphics(gfxScene->v3dViewer()->Driver()); foreachGraphicsObject(measure, [=](const GraphicsObjectPtr& gfxObject) { - gfxObject->SetZLayer(Graphic3d_ZLayerId_Topmost); - gfxScene->addObject(gfxObject); + gfxScene->addObject(gfxObject, GraphicsScene::AddObjectDisableSelectionMode); }); m_vecMeasureDisplay.push_back(std::move(measure)); diff --git a/src/app/widget_measure.h b/src/app/widget_measure.h index f451433d..d2171d2a 100644 --- a/src/app/widget_measure.h +++ b/src/app/widget_measure.h @@ -42,6 +42,7 @@ class WidgetMeasure : public QWidget { static LengthUnit toMeasureLengthUnit(int comboBoxId); static AngleUnit toMeasureAngleUnit(int comboBoxId); static AreaUnit toMeasureAreaUnit(int comboBoxId); + static VolumeUnit toMeasureVolumeUnit(int comboBoxId); MeasureType currentMeasureType() const; MeasureDisplayConfig currentMeasureDisplayConfig() const; diff --git a/src/app/widget_measure.ui b/src/app/widget_measure.ui index d5b2559f..0c60e0e2 100644 --- a/src/app/widget_measure.ui +++ b/src/app/widget_measure.ui @@ -6,8 +6,8 @@ 0 0 - 229 - 156 + 246 + 158 @@ -33,6 +33,40 @@ + + + + + Square Millimeter(mm²) + + + + + Square Centimeter(cm²) + + + + + Square Meter(m²) + + + + + Square Inch(in²) + + + + + Square Foot(ft²) + + + + + Square Yard(yd²) + + + + @@ -47,13 +81,6 @@ - - - - Angle Unit - - - @@ -102,6 +129,41 @@ + + + + Angle Unit + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + Volume Unit + + + @@ -144,61 +206,55 @@ Surface Area + + + Bounding Box + + - - + + - Square Millimeter(mm²) + Cubic Millimeter(mm³) - Square Centimeter(cm²) + Cubic Centimeter(cm³) - Square Meter(m²) + Cubic Meter(m³) - Square Inch(in²) + Cubic Inch(in³) - Square Foot(ft²) + Cubic Foot(ft³) - Square Yard(yd²) + Liter(L) - - - - - - - 0 - - - 0 - - - 0 + + + Imperial Gallon(GBgal) - - 0 + + + + US Gallon(USgal) - - - - + diff --git a/src/base/quantity.h b/src/base/quantity.h index 1a9d98cb..5ed8d4bf 100644 --- a/src/base/quantity.h +++ b/src/base/quantity.h @@ -108,6 +108,8 @@ constexpr QuantityVolume Quantity_CubicFoot(Quantity_Foot.value() * Quantity_Squ constexpr QuantityVelocity Quantity_MillimeterPerSecond(1.); constexpr QuantityVolume Quantity_Liter(1e6); +constexpr QuantityVolume Quantity_ImperialGallon(1e6 * 4.54609); +constexpr QuantityVolume Quantity_USGallon(1e6 * 3.785411784); constexpr QuantityMass Quantity_Microgram(1e-9); constexpr QuantityMass Quantity_Milligram(1e-6); diff --git a/src/base/unit.h b/src/base/unit.h index 16a0199f..748535fe 100644 --- a/src/base/unit.h +++ b/src/base/unit.h @@ -49,6 +49,15 @@ enum AreaUnit { SquareInch, SquareFoot, SquareYard, SquareMile }; +enum VolumeUnit { + // SI + CubicMillimeter, CubicCentimeter, CubicMeter, + // Imperial UK + CubicInch, CubicFoot, + // Others + Liter, ImperialGallon, USGallon +}; + #if 0 enum class BaseUnit { Length = 0, // Meter(m) diff --git a/src/base/unit_system.cpp b/src/base/unit_system.cpp index 6bff41f2..11697e21 100644 --- a/src/base/unit_system.cpp +++ b/src/base/unit_system.cpp @@ -341,6 +341,27 @@ UnitSystem::TranslateResult UnitSystem::translateArea(QuantityArea area, AreaUni return {}; } +UnitSystem::TranslateResult UnitSystem::translateVolume(QuantityVolume volume, VolumeUnit unit) +{ + auto fnTrResult = [=](QuantityVolume qtyFactor, const char* strUnit) { + return Internal::translateHelper(volume, qtyFactor, strUnit); + }; + switch (unit) { + // SI + case VolumeUnit::CubicMillimeter: return fnTrResult(Quantity_CubicMillimeter, "mm³"); + case VolumeUnit::CubicCentimeter: return fnTrResult(Quantity_CubicCentimeter, "cm³"); + case VolumeUnit::CubicMeter: return fnTrResult(Quantity_CubicMeter, "m³"); + // Imperial UK + case VolumeUnit::CubicInch: return fnTrResult(Quantity_CubicInch, "in³"); + case VolumeUnit::CubicFoot: return fnTrResult(Quantity_CubicFoot, "ft³"); + // Others + case VolumeUnit::Liter: return fnTrResult(Quantity_Liter, "L"); + case VolumeUnit::ImperialGallon: return fnTrResult(Quantity_ImperialGallon, "GBgal"); + case VolumeUnit::USGallon: return fnTrResult(Quantity_USGallon, "USgal"); + } + return {}; +} + UnitSystem::TranslateResult UnitSystem::translateAngle(QuantityAngle angle, AngleUnit unit) { switch (unit) { diff --git a/src/base/unit_system.h b/src/base/unit_system.h index e773e31a..54ab1577 100644 --- a/src/base/unit_system.h +++ b/src/base/unit_system.h @@ -32,6 +32,7 @@ class UnitSystem { static TranslateResult translateLength(QuantityLength length, LengthUnit unit); static TranslateResult translateArea(QuantityArea area, AreaUnit unit); + static TranslateResult translateVolume(QuantityVolume volume, VolumeUnit unit); static TranslateResult translateAngle(QuantityAngle angle, AngleUnit unit); static TranslateResult radians(QuantityAngle angle); diff --git a/src/graphics/graphics_scene.cpp b/src/graphics/graphics_scene.cpp index c3cc49db..a589f2dd 100644 --- a/src/graphics/graphics_scene.cpp +++ b/src/graphics/graphics_scene.cpp @@ -73,6 +73,15 @@ class InteractiveContext : public AIS_InteractiveContext { {} constexpr const GraphicsOwnerPtr& member_myLastPicked() const { return myLastPicked; } + + int defaultDisplayMode(const GraphicsObjectPtr& object) const + { + int displayMode; + int hilightMode; + int selMode; + this->GetDefModes(object, displayMode, hilightMode, selMode); + return displayMode; + } }; DEFINE_STANDARD_HANDLE(InteractiveContext, AIS_InteractiveContext) @@ -133,10 +142,20 @@ const opencascade::handle& GraphicsScene::drawerHighlight(Prs3d_Ty return d->m_aisContext->HighlightStyle(style); } -void GraphicsScene::addObject(const GraphicsObjectPtr& object) +void GraphicsScene::addObject(const GraphicsObjectPtr& object, AddObjectFlags flags) { - if (object) - d->m_aisContext->Display(object, false); + if (object) { + if ((flags & AddObjectDisableSelectionMode) != 0) { + const bool onEntry_AutoActivateSelection = d->m_aisContext->GetAutoActivateSelection(); + const int defaultDisplayMode = d->m_aisContext->defaultDisplayMode(object); + d->m_aisContext->SetAutoActivateSelection(false); + d->m_aisContext->Display(object, defaultDisplayMode, -1, false); + d->m_aisContext->SetAutoActivateSelection(onEntry_AutoActivateSelection); + } + else { + d->m_aisContext->Display(object, false); + } + } } void GraphicsScene::eraseObject(const GraphicsObjectPtr& object) diff --git a/src/graphics/graphics_scene.h b/src/graphics/graphics_scene.h index 1245e629..68ccc554 100644 --- a/src/graphics/graphics_scene.h +++ b/src/graphics/graphics_scene.h @@ -37,7 +37,13 @@ class GraphicsScene { const opencascade::handle& drawerDefault() const; const opencascade::handle& drawerHighlight(Prs3d_TypeOfHighlight style) const; - void addObject(const GraphicsObjectPtr& object); + enum AddObjectFlag { + AddObjectDefault = 0, + AddObjectDisableSelectionMode = 1 + }; + using AddObjectFlags = unsigned; + + void addObject(const GraphicsObjectPtr& object, AddObjectFlags flags = AddObjectDefault); void eraseObject(const GraphicsObjectPtr& object); void redraw(); diff --git a/src/measure/measure_display.cpp b/src/measure/measure_display.cpp index 62f24d63..c0c12e15 100644 --- a/src/measure/measure_display.cpp +++ b/src/measure/measure_display.cpp @@ -6,10 +6,14 @@ #include "measure_display.h" +#include "../base/occ_handle.h" #include "../base/text_id.h" #include "../base/unit_system.h" #include "measure_tool.h" +#include +#include +#include #include #include #include @@ -19,6 +23,7 @@ #include #include +#include namespace Mayo { @@ -44,6 +49,8 @@ std::unique_ptr BaseMeasureDisplay::createFrom(MeasureType type return std::make_unique(std::get(value)); case MeasureType::Area: return std::make_unique(std::get(value)); + case MeasureType::BoundingBox: + return std::make_unique(std::get(value)); default: return {}; } @@ -133,6 +140,7 @@ void BaseMeasureDisplay::applyGraphicsDefaults(IMeasureDisplay* measureDisplay) { for (int i = 0; i < measureDisplay->graphicsObjectsCount(); ++i) { auto gfxObject = measureDisplay->graphicsObjectAt(i); + gfxObject->SetZLayer(Graphic3d_ZLayerId_Topmost); auto gfxText = Handle(AIS_TextLabel)::DownCast(gfxObject); if (gfxText) { gfxText->SetDisplayType(Aspect_TODT_SUBTITLE); @@ -448,4 +456,81 @@ void MeasureDisplayArea::sumAdd(const IMeasureDisplay& other) BaseMeasureDisplay::sumAdd(other); } +// -- +// -- Bounding Box +// -- + +MeasureDisplayBoundingBox::MeasureDisplayBoundingBox(const MeasureBoundingBox& bnd) + : m_bnd(bnd), + m_gfxXLengthText(new AIS_TextLabel), + m_gfxYLengthText(new AIS_TextLabel), + m_gfxZLengthText(new AIS_TextLabel) +{ + m_gfxMinPoint = new AIS_Point(new Geom_CartesianPoint(bnd.cornerMin)); + m_gfxMaxPoint = new AIS_Point(new Geom_CartesianPoint(bnd.cornerMax)); + + const TopoDS_Shape shapeBox = BRepPrimAPI_MakeBox(bnd.cornerMin, bnd.cornerMax); + auto aisShapeBox = new AIS_Shape(shapeBox); + m_gfxBox = aisShapeBox; + m_gfxBox->SetDisplayMode(AIS_Shaded); + m_gfxBox->SetTransparency(0.85); + m_gfxBox->Attributes()->SetFaceBoundaryDraw(true); + m_gfxBox->Attributes()->SetFaceBoundaryAspect(new Prs3d_LineAspect({}, Aspect_TOL_DOT, 1.)); + + const double diffX = bnd.cornerMax.X() - bnd.cornerMin.X(); + const double diffY = bnd.cornerMax.Y() - bnd.cornerMin.Y(); + const double diffZ = bnd.cornerMax.Z() - bnd.cornerMin.Z(); + m_gfxXLengthText->SetPosition(bnd.cornerMin.Translated((diffX / 2.) * gp_Vec{1, 0, 0})); + m_gfxYLengthText->SetPosition(bnd.cornerMin.Translated((diffY / 2.) * gp_Vec{0, 1, 0})); + m_gfxZLengthText->SetPosition(bnd.cornerMin.Translated((diffZ / 2.) * gp_Vec{0, 0, 1})); + + BaseMeasureDisplay::applyGraphicsDefaults(this); + m_gfxBox->SetColor(Quantity_NOC_BEIGE); + m_gfxBox->SetZLayer(Graphic3d_ZLayerId_Default); +} + +void MeasureDisplayBoundingBox::update(const MeasureDisplayConfig& config) +{ + const auto trXLength = UnitSystem::translateLength(m_bnd.xLength, config.lengthUnit); + const auto trYLength = UnitSystem::translateLength(m_bnd.yLength, config.lengthUnit); + const auto trZLength = UnitSystem::translateLength(m_bnd.zLength, config.lengthUnit); + const auto trVolume = UnitSystem::translateVolume(m_bnd.volume, config.volumeUnit); + this->setText(fmt::format( + MeasureDisplayI18N::textIdTr( + "Min point: {0}
Max point: {1}
" + "Size: {2} x {3} x {4}{5}
" + "Volume: {6}{7}" + ), + BaseMeasureDisplay::text(m_bnd.cornerMin, config), + BaseMeasureDisplay::text(m_bnd.cornerMax, config), + BaseMeasureDisplay::text(trXLength, config), + BaseMeasureDisplay::text(trYLength, config), + BaseMeasureDisplay::text(trZLength, config), + trXLength.strUnit, + BaseMeasureDisplay::text(trVolume, config), + trVolume.strUnit + )); + + m_gfxXLengthText->SetText(to_OccExtString(BaseMeasureDisplay::text(trXLength, config))); + m_gfxYLengthText->SetText(to_OccExtString(BaseMeasureDisplay::text(trYLength, config))); + m_gfxZLengthText->SetText(to_OccExtString(BaseMeasureDisplay::text(trZLength, config))); + BaseMeasureDisplay::adaptScale(m_gfxXLengthText, config); + BaseMeasureDisplay::adaptScale(m_gfxYLengthText, config); + BaseMeasureDisplay::adaptScale(m_gfxZLengthText, config); +} + +GraphicsObjectPtr MeasureDisplayBoundingBox::graphicsObjectAt(int i) const +{ + switch (i) { + case 0: return m_gfxMinPoint; + case 1: return m_gfxMaxPoint; + case 2: return m_gfxBox; + case 3: return m_gfxXLengthText; + case 4: return m_gfxYLengthText; + case 5: return m_gfxZLengthText; + } + + return {}; +} + } // namespace Mayo diff --git a/src/measure/measure_display.h b/src/measure/measure_display.h index 23aa0be5..4cb70713 100644 --- a/src/measure/measure_display.h +++ b/src/measure/measure_display.h @@ -29,6 +29,7 @@ struct MeasureDisplayConfig { LengthUnit lengthUnit = LengthUnit::Millimeter; AngleUnit angleUnit = AngleUnit::Degree; AreaUnit areaUnit = AreaUnit::SquareMillimeter; + VolumeUnit volumeUnit = VolumeUnit::CubicMillimeter; DoubleToStringOptions doubleToStringOptions; double devicePixelRatio = 1.; }; @@ -228,4 +229,25 @@ class MeasureDisplayArea : public BaseMeasureDisplay { Handle_AIS_TextLabel m_gfxAreaText; }; +// -- +// -- Bounding Box +// -- + +class MeasureDisplayBoundingBox : public BaseMeasureDisplay { +public: + MeasureDisplayBoundingBox(const MeasureBoundingBox& bnd); + void update(const MeasureDisplayConfig& config) override; + int graphicsObjectsCount() const override { return 6; } + GraphicsObjectPtr graphicsObjectAt(int i) const override; + +private: + MeasureBoundingBox m_bnd; + Handle_AIS_Point m_gfxMinPoint; + Handle_AIS_Point m_gfxMaxPoint; + Handle_AIS_InteractiveObject m_gfxBox; + Handle_AIS_TextLabel m_gfxXLengthText; + Handle_AIS_TextLabel m_gfxYLengthText; + Handle_AIS_TextLabel m_gfxZLengthText; +}; + } // namespace Mayo diff --git a/src/measure/measure_tool.cpp b/src/measure/measure_tool.cpp index 2c688a72..a78a6e91 100644 --- a/src/measure/measure_tool.cpp +++ b/src/measure/measure_tool.cpp @@ -9,7 +9,8 @@ namespace Mayo { MeasureValue IMeasureTool_computeValue( - const IMeasureTool& tool, MeasureType type, const GraphicsOwnerPtr& owner) + const IMeasureTool& tool, MeasureType type, const GraphicsOwnerPtr& owner + ) { MeasureValue value; switch (type) { @@ -22,6 +23,8 @@ MeasureValue IMeasureTool_computeValue( return tool.length(owner); case MeasureType::Area: return tool.area(owner); + case MeasureType::BoundingBox: + return tool.boundingBox(owner); default: return value; } // endswitch @@ -31,7 +34,8 @@ MeasureValue IMeasureTool_computeValue( const IMeasureTool& tool, MeasureType type, const GraphicsOwnerPtr& owner1, - const GraphicsOwnerPtr& owner2) + const GraphicsOwnerPtr& owner2 + ) { MeasureValue value; switch (type) { diff --git a/src/measure/measure_tool.h b/src/measure/measure_tool.h index 2b56d6b5..261cc36a 100644 --- a/src/measure/measure_tool.h +++ b/src/measure/measure_tool.h @@ -80,7 +80,21 @@ struct MeasureArea { QuantityArea value; }; - +// Measure of the bounding box of an entity +struct MeasureBoundingBox { + // Lower corner of the bounding box + gp_Pnt cornerMin; + // Upper corner of the bounding box + gp_Pnt cornerMax; + // Length of the bounding box along the X-axis + QuantityLength xLength; + // Length of the bounding box along the Y-axis + QuantityLength yLength; + // Length of the bounding box along the Z-axis + QuantityLength zLength; + // Volume of the bounding box + QuantityVolume volume; +}; // Provides an interface to various measurement services // Input data of a measure service is one or many graphics entities pointed to by GraphicsOwner objects @@ -99,6 +113,7 @@ class IMeasureTool { virtual MeasureAngle angle(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const = 0; virtual MeasureLength length(const GraphicsOwnerPtr& owner) const = 0; virtual MeasureArea area(const GraphicsOwnerPtr& owner) const = 0; + virtual MeasureBoundingBox boundingBox(const GraphicsOwnerPtr& owner) const = 0; }; // Base interface for errors reported by measurement services of IMeasureTool @@ -115,7 +130,8 @@ using MeasureValue = std::variant< MeasureDistance, MeasureAngle, MeasureLength, - MeasureArea + MeasureArea, + MeasureBoundingBox >; bool MeasureValue_isValid(const MeasureValue& res); diff --git a/src/measure/measure_tool_brep.cpp b/src/measure/measure_tool_brep.cpp index f4132fb7..755ba340 100644 --- a/src/measure/measure_tool_brep.cpp +++ b/src/measure/measure_tool_brep.cpp @@ -15,9 +15,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -57,7 +59,8 @@ enum class ErrorCode { NotAllEdges, NotLinearEdge, NotAllFaces, - ParallelEdges + ParallelEdges, + BoundingBoxIsVoid }; template @@ -89,6 +92,8 @@ class BRepMeasureError : public IMeasureError { return textIdTr("All entities must be faces"); case ErrorCode::ParallelEdges: return textIdTr("Entities must not be parallel"); + case ErrorCode::BoundingBoxIsVoid: + return textIdTr("Bounding box computed is void"); default: return textIdTr("Unknown error"); } @@ -181,6 +186,14 @@ Span MeasureToolBRep::selectionModes(MeasureT static const GraphicsObjectSelectionMode modes[] = { AIS_Shape::SelectionMode(TopAbs_FACE) }; return modes; } + case MeasureType::BoundingBox: { + static const GraphicsObjectSelectionMode modes[] = { + //AIS_Shape::SelectionMode(TopAbs_FACE), + //AIS_Shape::SelectionMode(TopAbs_SOLID) + AIS_Shape::SelectionMode(TopAbs_COMPOUND) + }; + return modes; + } default: { return {}; } @@ -233,6 +246,11 @@ MeasureArea MeasureToolBRep::area(const GraphicsOwnerPtr& owner) const return brepArea(getShape(owner)); } +MeasureBoundingBox MeasureToolBRep::boundingBox(const GraphicsOwnerPtr& owner) const +{ + return brepBoundingBox(getShape(owner)); +} + gp_Pnt MeasureToolBRep::brepVertexPosition(const TopoDS_Shape& shape) { throwErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_VERTEX); @@ -509,4 +527,31 @@ MeasureArea MeasureToolBRep::brepArea(const TopoDS_Shape& shape) return areaResult; } +MeasureBoundingBox MeasureToolBRep::brepBoundingBox(const TopoDS_Shape& shape) +{ + MeasureBoundingBox measure; +#if 0 + Bnd_OBB bnd; + BRepBndLib::AddOBB(shape, bnd); + //BRepBndLib::AddOBB(shape, bnd, false/*!useTriangulation*/, true/*optimal*/, false/*!useShapeTolerance*/); + + gp_Pnt points[8] = {}; + bnd.GetVertex(points); + measure.cornerMin = points[0]; + measure.cornerMax = points[7]; + //measure.isAxisAligned = bnd.IsAABox(); +#else + Bnd_Box bnd; + BRepBndLib::AddOptimal(shape, bnd); + throwErrorIf(bnd.IsVoid()); + measure.cornerMin = bnd.CornerMin(); + measure.cornerMax = bnd.CornerMax(); + measure.xLength = std::abs(measure.cornerMax.X() - measure.cornerMin.X()) * Quantity_Millimeter; + measure.yLength = std::abs(measure.cornerMax.Y() - measure.cornerMin.Y()) * Quantity_Millimeter; + measure.zLength = std::abs(measure.cornerMax.Z() - measure.cornerMin.Z()) * Quantity_Millimeter; + measure.volume = measure.xLength * measure.yLength * measure.zLength; +#endif + return measure; +} + } // namespace Mayo diff --git a/src/measure/measure_tool_brep.h b/src/measure/measure_tool_brep.h index 777fb76b..903af38c 100644 --- a/src/measure/measure_tool_brep.h +++ b/src/measure/measure_tool_brep.h @@ -27,6 +27,7 @@ class MeasureToolBRep : public IMeasureTool { MeasureAngle angle(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const override; MeasureLength length(const GraphicsOwnerPtr& owner) const override; MeasureArea area(const GraphicsOwnerPtr& owner) const override; + MeasureBoundingBox boundingBox(const GraphicsOwnerPtr& owner) const override; static gp_Pnt brepVertexPosition(const TopoDS_Shape& shape); static MeasureCircle brepCircle(const TopoDS_Shape& shape); @@ -35,6 +36,7 @@ class MeasureToolBRep : public IMeasureTool { static MeasureAngle brepAngle(const TopoDS_Shape& shape1, const TopoDS_Shape& shape2); static MeasureLength brepLength(const TopoDS_Shape& shape); static MeasureArea brepArea(const TopoDS_Shape& shape); + static MeasureBoundingBox brepBoundingBox(const TopoDS_Shape& shape); private: static MeasureCircle brepCircleFromGeometricEdge(const TopoDS_Edge& edge); diff --git a/src/measure/measure_type.h b/src/measure/measure_type.h index b1bb640d..9c5b6442 100644 --- a/src/measure/measure_type.h +++ b/src/measure/measure_type.h @@ -9,7 +9,16 @@ namespace Mayo { enum class MeasureType { - None, VertexPosition, CircleCenter, CircleDiameter, MinDistance, CenterDistance, Angle, Length, Area + None, + VertexPosition, + CircleCenter, + CircleDiameter, + MinDistance, + CenterDistance, + Angle, + Length, + Area, + BoundingBox }; } // namespace Mayo diff --git a/tests/test_measure.cpp b/tests/test_measure.cpp index 5c2a6606..061f93e3 100644 --- a/tests/test_measure.cpp +++ b/tests/test_measure.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -174,7 +175,7 @@ void TestMeasure::BRepLength_PolygonEdge_test() QCOMPARE(UnitSystem::millimeters(len.value).value, 24.); } -void TestMeasure::BRepArea_TriangulationFace() +void TestMeasure::BRepArea_TriangulationFace_test() { auto progress = &TaskProgress::null(); IO::OccStlReader reader; @@ -189,4 +190,23 @@ void TestMeasure::BRepArea_TriangulationFace() QVERIFY(std::abs(double(UnitSystem::squareMillimeters(area.value)) - 597.6224) < 0.0001); } +void TestMeasure::BRepBoundingBox_Sphere_test() +{ + const double sphereRadius = 50.; + const TopoDS_Shape sphereShape = BRepPrimAPI_MakeSphere(50.); + const MeasureBoundingBox bndBox = MeasureToolBRep::brepBoundingBox(sphereShape); + QVERIFY(bndBox.cornerMin.IsEqual(gp_Pnt{-sphereRadius, -sphereRadius, -sphereRadius}, Precision::Confusion())); + QVERIFY(bndBox.cornerMax.IsEqual(gp_Pnt{sphereRadius, sphereRadius, sphereRadius}, Precision::Confusion())); + QCOMPARE(double(UnitSystem::millimeters(bndBox.xLength)), 2 * sphereRadius); + QCOMPARE(double(UnitSystem::millimeters(bndBox.yLength)), 2 * sphereRadius); + QCOMPARE(double(UnitSystem::millimeters(bndBox.zLength)), 2 * sphereRadius); + QCOMPARE(double(UnitSystem::cubicMillimeters(bndBox.volume)), 8 * sphereRadius * sphereRadius * sphereRadius); +} + +void TestMeasure::BRepBoundingBox_NullShape_test() +{ + const TopoDS_Shape nullShape; + QVERIFY_EXCEPTION_THROWN(MeasureToolBRep::brepBoundingBox(nullShape), IMeasureError); +} + } // namespace Mayo diff --git a/tests/test_measure.h b/tests/test_measure.h index c66d9756..21324679 100644 --- a/tests/test_measure.h +++ b/tests/test_measure.h @@ -32,7 +32,10 @@ private slots: void BRepLength_PolygonEdge_test(); - void BRepArea_TriangulationFace(); + void BRepArea_TriangulationFace_test(); + + void BRepBoundingBox_Sphere_test(); + void BRepBoundingBox_NullShape_test(); }; } // namespace Mayo From 8e3302fe93d7092913f2e061bc32866b26be3501 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 29 Jan 2024 12:16:08 +0100 Subject: [PATCH 009/101] Mention new "bounding box" measurement in README[skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8da1032..5b130bf1 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ There's also a complete [video](https://www.youtube.com/watch?v=qg6IamnlfxE&ab_c - **3D exploding of the model tree** allowing better exploration of complex designs -- **3D measure tools** for circles, angles, lengths, areas, ... +- **3D measure tools** for circles, angles, lengths, areas, bounding box, ... - **3D view cube** providing intuitive camera manipulation From 6eb62e907d24da80680a29e9be0456ea0c1a1864 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Tue, 6 Feb 2024 12:06:04 +0100 Subject: [PATCH 010/101] CI: add build for Linux aarch64/armv7 Relates to GitHub #256 --- .github/workflows/ci_linux_arm.yml | 81 +++++++++++++++++++ mayo.pro | 30 +++++-- src/app/command_system_information.cpp | 56 +++---------- .../command_system_information_occopengl.cpp | 61 ++++++++++++++ .../command_system_information_occopengl.h | 20 +++++ 5 files changed, 196 insertions(+), 52 deletions(-) create mode 100644 .github/workflows/ci_linux_arm.yml create mode 100644 src/app/command_system_information_occopengl.cpp create mode 100644 src/app/command_system_information_occopengl.h diff --git a/.github/workflows/ci_linux_arm.yml b/.github/workflows/ci_linux_arm.yml new file mode 100644 index 00000000..ff73e7b8 --- /dev/null +++ b/.github/workflows/ci_linux_arm.yml @@ -0,0 +1,81 @@ +name: Linux ARM + +on: [push] + +jobs: + build-linux-arm: + strategy: + matrix: + arch: [armv7, aarch64] + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Get current branch name + uses: nelonoel/branch-name@v1.0.1 + + - name: Run Build + uses: uraimo/run-on-arch-action@v2 + with: + arch: ${{matrix.arch}} + distro: ubuntu22.04 + githubToken: ${{github.token}} # Not required, but speeds up builds + install: | + apt-get -y update + apt-get -y install xvfb # Needed to run unit tests + apt-get -y install git + apt-get -y install g++ + apt-get -y install qtbase5-dev libqt5svg5-dev + apt-get -y install libxcb-cursor0 # Dependencies for "xcb" Qt plugin(actually needed for Qt6) + apt-get -y install libocct-data-exchange-dev libocct-draw-dev + apt-get -y install libassimp-dev + + env: | + DISPLAY: :0 + GIT_BRANCH_NAME: ${{env.BRANCH_NAME}} + RUN_ARCH_NAME: ${{matrix.arch}} + + run: | + GH_CASCADE_INC_DIR=`dpkg -L libocct-foundation-dev | grep -i "Standard_Version.hxx" | sed "s/\/Standard_Version.hxx//i"` + GH_CASCADE_LIB_DIR=`dpkg -L libocct-foundation-dev | grep -i "libTKernel.so" | sed "s/\/libTKernel.so//i"` + GH_ASSIMP_INC_DIR=`dpkg -L libassimp-dev | grep -i "version.h" | sed "s/\/version.h//i"` + GH_ASSIMP_LIB_DIR=`dpkg -L libassimp-dev | grep -i "libassimp.so" | sed "s/\/libassimp.so//i"` + git clone https://github.com/fougue/mayo.git + cd mayo + echo GIT_BRANCH_NAME=$GIT_BRANCH_NAME + git checkout $GIT_BRANCH_NAME + cd .. + mkdir build-mayo + cd build-mayo + echo GH_CASCADE_INC_DIR=$GH_CASCADE_INC_DIR + echo GH_CASCADE_LIB_DIR=$GH_CASCADE_LIB_DIR + echo GH_ASSIMP_INC_DIR=$GH_ASSIMP_INC_DIR + echo GH_ASSIMP_LIB_DIR=$GH_ASSIMP_LIB_DIR + [ ! -d $GH_CASCADE_INC_DIR ] && echo "ERROR: OpenCascade include dir doesn't exist" + [ ! -d $GH_CASCADE_LIB_DIR ] && echo "ERROR: OpenCascade lib dir doesn't exist" + [ ! -d $GH_ASSIMP_INC_DIR ] && echo "ERROR: assimp include dir doesn't exist" + [ ! -d $GH_ASSIMP_LIB_DIR ] && echo "ERROR: assimp lib dir doesn't exist" + qmake ../mayo \ + CONFIG+=debug \ + CONFIG+=withtests \ + CASCADE_INC_DIR=$GH_CASCADE_INC_DIR \ + CASCADE_LIB_DIR=$GH_CASCADE_LIB_DIR \ + ASSIMP_INC_DIR=$GH_ASSIMP_INC_DIR \ + ASSIMP_LIB_DIR=$GH_ASSIMP_LIB_DIR + make -j`nproc` + # + # Execute Unit Tests + # Note: for some unknown reason unit tests are crashing for armv7(on very first test) + # so just execute them for aarch64 + # + if [ $RUN_ARCH_NAME = 'aarch64' ]; then + # Start X11 display server + Xvfb $DISPLAY -screen 0 1280x1024x24 & + sleep 5s + ./mayo --runtests + else + echo "NOTE: unit tests skipped, they need to be fixed for this architecture" + fi diff --git a/mayo.pro b/mayo.pro index f91049b1..62b50120 100644 --- a/mayo.pro +++ b/mayo.pro @@ -19,14 +19,8 @@ message(Qt version $$QT_VERSION) error(Qt >= 5.14 is required but detected version is $$QT_VERSION) } -# Check architecture -equals(QT_ARCH, x86_64) { - # NOP -} else:equals(QT_ARCH, i386) { - # NOP -} else { - warning(Platform architecture may be not supported(QT_ARCH = $$QT_ARCH)) -} +# If variable 'CHECK_ARCH_64' is empty then host is not 64bit +CHECK_ARCH_64 = $$find(QT_ARCH, 64) QT += core gui widgets greaterThan(QT_MAJOR_VERSION, 5) { @@ -60,15 +54,33 @@ msvc { DEFINES += _USE_MATH_DEFINES } } + gcc|clang { QMAKE_CXXFLAGS += -std=c++17 } + clang { # Silent Clang warnings about instantiation of variable 'Mayo::GenericProperty::TypeName' QMAKE_CXXFLAGS += -Wno-undefined-var-template # See https://libcxx.llvm.org/docs/UsingLibcxx.html # LIBS += -lstdc++fs } + +unix:isEmpty(CHECK_ARCH_64) { + macx { + DEFINES += _DARWIN_USE_64_BIT_INODE + } else { + DEFINES += _FILE_OFFSET_BITS=64 _LARGEFILE64_SOURCE=1 + } + + gcc|clang:contains(QT_ARCH, arm) { + # See: + # https://stackoverflow.com/questions/48149323/what-does-the-gcc-warning-project-parameter-passing-for-x-changed-in-gcc-7-1-m + # https://stackoverflow.com/questions/52020305/what-exactly-does-gccs-wpsabi-option-do-what-are-the-implications-of-supressi + QMAKE_CXXFLAGS += -Wno-psabi + } +} + macx { DEFINES += GL_SILENCE_DEPRECATION QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.15 @@ -76,6 +88,7 @@ macx { # QMAKE_CXXFLAGS += -mmacosx-version-min=10.15 ICON = images/appicon.icns } + win32 { LIBS += -lOpengl32 -lUser32 } @@ -124,6 +137,7 @@ RC_ICONS = images/appicon.ico OTHER_FILES += \ README.md \ .github/workflows/ci_linux.yml \ + .github/workflows/ci_linux_arm.yml \ .github/workflows/ci_macos.yml \ .github/workflows/ci_windows.yml \ images/credits.txt \ diff --git a/src/app/command_system_information.cpp b/src/app/command_system_information.cpp index 5d149644..5fb843f9 100644 --- a/src/app/command_system_information.cpp +++ b/src/app/command_system_information.cpp @@ -7,6 +7,7 @@ #include "commands_help.h" #include "app_module.h" +#include "command_system_information_occopengl.h" #include "qstring_conv.h" #include "qtwidgets_utils.h" #include "version.h" @@ -34,20 +35,12 @@ #include #include -// NOTICE for Linux/X11 -// Because of #define conflicts, OpenGL_Context.hxx must be included *before* QtGui/QOpenGLContext -// It also has to be included *after* QtCore/QTextStream -// Beware of these limitations when adding/removing inclusion of headers here -#include #include #include #include -#ifdef HAVE_GMIO -# include -#endif - +#include #include #include @@ -175,43 +168,18 @@ static void dumpOpenGlInfo(QTextStream& str) window.create(); qtContext.makeCurrent(&window); - OpenGl_Context occContext; - if (!occContext.Init()) { - str << "Unable to initialize OpenGl_Context object" << '\n'; + try { + auto infos = Internal::getOccOpenGlInfos(); + for (const auto& [key, value] : infos) { + str << indent << key << ": "; + std::visit([&](const auto& arg) { str << arg; }, value); + str << '\n'; + } + } + catch (const std::exception& error) { + str << error.what() << '\n'; return; } - - TColStd_IndexedDataMapOfStringString dict; - occContext.DiagnosticInformation(dict, Graphic3d_DiagnosticInfo_Basic); - for (TColStd_IndexedDataMapOfStringString::Iterator it(dict); it.More(); it.Next()) - str << indent << to_QString(it.Key()) << ": " << to_QString(it.Value()) << '\n'; - - str << indent << "MaxDegreeOfAnisotropy: " << occContext.MaxDegreeOfAnisotropy() << '\n' - << indent << "MaxDrawBuffers: " << occContext.MaxDrawBuffers() << '\n' - << indent << "MaxClipPlanes: " << occContext.MaxClipPlanes() << '\n' - << indent << "HasRayTracing: " << occContext.HasRayTracing() << '\n' - << indent << "HasRayTracingTextures: " << occContext.HasRayTracingTextures() << '\n' - << indent << "HasRayTracingAdaptiveSampling: " << occContext.HasRayTracingAdaptiveSampling() << '\n' - << indent << "UseVBO: " << occContext.ToUseVbo() << '\n'; - -#if OCC_VERSION_HEX >= 0x070400 - str << indent << "MaxDumpSizeX: " << occContext.MaxDumpSizeX() << '\n' - << indent << "MaxDumpSizeY: " << occContext.MaxDumpSizeY() << '\n' - << indent << "HasRayTracingAdaptiveSamplingAtomic: " << occContext.HasRayTracingAdaptiveSamplingAtomic() << '\n'; -#endif - -#if OCC_VERSION_HEX >= 0x070500 - str << indent << "HasTextureBaseLevel: " << occContext.HasTextureBaseLevel() << '\n' - << indent << "HasSRGB: " << occContext.HasSRGB() << '\n' - << indent << "RenderSRGB: " << occContext.ToRenderSRGB() << '\n' - << indent << "IsWindowSRGB: " << occContext.IsWindowSRGB() << '\n' - << indent << "HasPBR: " << occContext.HasPBR() << '\n'; -#endif - -#if OCC_VERSION_HEX >= 0x070700 - str << indent << "GraphicsLibrary: " << MetaEnum::name(occContext.GraphicsLibrary()) << '\n' - << indent << "HasTextureMultisampling: " << occContext.HasTextureMultisampling() << '\n'; -#endif } QString CommandSystemInformation::data() diff --git a/src/app/command_system_information_occopengl.cpp b/src/app/command_system_information_occopengl.cpp new file mode 100644 index 00000000..6926565c --- /dev/null +++ b/src/app/command_system_information_occopengl.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "command_system_information_occopengl.h" + +#include "../base/meta_enum.h" +#include "../base/string_conv.h" + +#include +#include + +namespace Mayo::Internal { + +using InfoValue = std::variant; +std::map getOccOpenGlInfos() +{ + OpenGl_Context occContext; + if (!occContext.Init()) + throw std::runtime_error("Unable to initialize OpenGl_Context object"); + + std::map infos; + + TColStd_IndexedDataMapOfStringString dict; + occContext.DiagnosticInformation(dict, Graphic3d_DiagnosticInfo_Basic); + for (TColStd_IndexedDataMapOfStringString::Iterator it(dict); it.More(); it.Next()) + infos[to_stdString(it.Key())] = to_stdString(it.Value()); + + infos["MaxDegreeOfAnisotropy"] = occContext.MaxDegreeOfAnisotropy(); + infos["MaxDrawBuffers"] = occContext.MaxDrawBuffers(); + infos["MaxClipPlanes"] = occContext.MaxClipPlanes(); + infos["HasRayTracing"] = occContext.HasRayTracing(); + infos["HasRayTracingTextures"] = occContext.HasRayTracingTextures(); + infos["HasRayTracingAdaptiveSampling"] = occContext.HasRayTracingAdaptiveSampling(); + infos["UseVBO"] = occContext.ToUseVbo();; + +#if OCC_VERSION_HEX >= 0x070400 + infos["MaxDumpSizeX"] = occContext.MaxDumpSizeX(); + infos["MaxDumpSizeY"] = occContext.MaxDumpSizeY(); + infos["HasRayTracingAdaptiveSamplingAtomic"] = occContext.HasRayTracingAdaptiveSamplingAtomic();; +#endif + +#if OCC_VERSION_HEX >= 0x070500 + infos["HasTextureBaseLevel"] = occContext.HasTextureBaseLevel(); + infos["HasSRGB"] = occContext.HasSRGB(); + infos["RenderSRGB"] = occContext.ToRenderSRGB(); + infos["IsWindowSRGB"] = occContext.IsWindowSRGB(); + infos["HasPBR"] = occContext.HasPBR();; +#endif + +#if OCC_VERSION_HEX >= 0x070700 + infos["GraphicsLibrary"] = std::string{MetaEnum::name(occContext.GraphicsLibrary())}; + infos["HasTextureMultisampling"] = occContext.HasTextureMultisampling();; +#endif + + return infos; +} + +} // namespace Mayo::Internal diff --git a/src/app/command_system_information_occopengl.h b/src/app/command_system_information_occopengl.h new file mode 100644 index 00000000..9f2f162f --- /dev/null +++ b/src/app/command_system_information_occopengl.h @@ -0,0 +1,20 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +// Auxiliary file to avoid OpenGL macros collisions between Qt and OpenCascade headers. + +#include +#include +#include + +namespace Mayo::Internal { + +using InfoValue = std::variant; +std::map getOccOpenGlInfos(); + +} // namespace Mayo::Internal From e231fd90f42e61db7010a91db9512b7a5131e4d5 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 8 Feb 2024 18:14:20 +0100 Subject: [PATCH 011/101] App: indentation fixes --- src/app/widget_model_tree_builder_xde.cpp | 15 ++++++++++----- src/app/widget_model_tree_builder_xde.h | 3 ++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/app/widget_model_tree_builder_xde.cpp b/src/app/widget_model_tree_builder_xde.cpp index 8f7df976..01e5277c 100644 --- a/src/app/widget_model_tree_builder_xde.cpp +++ b/src/app/widget_model_tree_builder_xde.cpp @@ -96,7 +96,8 @@ void WidgetModelTreeBuilder_Xde::refreshTextTreeItem( XCAFDoc_ShapeTool::GetUsers( XCaf::shapeReferred(labelNode), seqLabelRefresh, - false /* don't get sub children */); + false /* don't get sub children */ + ); } else { seqLabelRefresh.Append(labelNode); @@ -144,7 +145,8 @@ WidgetModelTree_UserActions WidgetModelTreeBuilder_Xde::createUserActions(QObjec } QTreeWidgetItem* WidgetModelTreeBuilder_Xde::guiCreateXdeTreeNode( - QTreeWidgetItem* guiParentNode, const DocumentTreeNode& node) + QTreeWidgetItem* guiParentNode, const DocumentTreeNode& node + ) { auto guiNode = new QTreeWidgetItem(guiParentNode); const QString stdName = to_QString(CafUtils::labelAttrStdName(node.label())); @@ -158,7 +160,8 @@ QTreeWidgetItem* WidgetModelTreeBuilder_Xde::guiCreateXdeTreeNode( } QTreeWidgetItem* WidgetModelTreeBuilder_Xde::buildXdeTree( - QTreeWidgetItem* treeItem, const DocumentTreeNode& node) + QTreeWidgetItem* treeItem, const DocumentTreeNode& node + ) { Expects(node.isEntity()); @@ -249,7 +252,8 @@ void WidgetModelTreeBuilder_Xde::refreshXdeAssemblyNodeItemText(QTreeWidgetItem* } QString WidgetModelTreeBuilder_Xde::referenceItemText( - const TDF_Label& instanceLabel, const TDF_Label& productLabel) const + const TDF_Label& instanceLabel, const TDF_Label& productLabel + ) const { const QString instanceName = to_QString(CafUtils::labelAttrStdName(instanceLabel)).trimmed(); const QString productName = to_QString(CafUtils::labelAttrStdName(productLabel)).trimmed(); @@ -261,7 +265,8 @@ QString WidgetModelTreeBuilder_Xde::referenceItemText( } QTreeWidgetItem* WidgetModelTreeBuilder_Xde::findTreeItem( - QTreeWidgetItem* parentTreeItem, const TDF_Label& label) const + QTreeWidgetItem* parentTreeItem, const TDF_Label& label + ) const { if (!parentTreeItem) return nullptr; diff --git a/src/app/widget_model_tree_builder_xde.h b/src/app/widget_model_tree_builder_xde.h index 5c0898c2..917b564a 100644 --- a/src/app/widget_model_tree_builder_xde.h +++ b/src/app/widget_model_tree_builder_xde.h @@ -28,7 +28,8 @@ class WidgetModelTreeBuilder_Xde : public WidgetModelTreeBuilder { using ThisType = WidgetModelTreeBuilder_Xde; static QTreeWidgetItem* guiCreateXdeTreeNode( - QTreeWidgetItem* guiParentNode, const DocumentTreeNode& node); + QTreeWidgetItem* guiParentNode, const DocumentTreeNode& node + ); QTreeWidgetItem* buildXdeTree(QTreeWidgetItem* treeItem, const DocumentTreeNode& node); void refreshXdeAssemblyNodeItemText(QTreeWidgetItem* item); From 7445a2e9e3325b26f6fc735e3005aef2d721e692 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 8 Feb 2024 18:46:04 +0100 Subject: [PATCH 012/101] Base: fix possible data race when importing file Relates to GitHub #254 --- src/base/document.cpp | 46 ++++++++++++++++++++++++++++-------------- src/base/document.h | 3 +++ src/base/io_system.cpp | 14 +++++++------ 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/base/document.cpp b/src/base/document.cpp index 915e21cd..67e51a42 100644 --- a/src/base/document.cpp +++ b/src/base/document.cpp @@ -143,27 +143,43 @@ TDF_Label Document::newEntityShapeLabel() return m_xcaf.shapeTool()->NewShape(); } -void Document::addEntityTreeNode(const TDF_Label& label) +TreeNodeId Document::findEntity(const TDF_Label& label) const { - // Check if 'label' belongs to current document - if (Document::findFrom(label).get() != this) - return; - - // Check if 'label' is not already there inside model tree for (int i = 0; i < this->entityCount(); ++i) { if (this->entityLabel(i) == label) - return; + return this->entityTreeNodeId(i); } + return 0; +} + +bool Document::containsLabel(const TDF_Label &label) const +{ + return Document::findFrom(label).get() == this; +} + +void Document::addEntityTreeNode(const TDF_Label& label) +{ // TODO Allow custom population of the model tree for the new entity - const TreeNodeId nodeId = m_xcaf.deepBuildAssemblyTree(0, label); - this->signalEntityAdded.send(nodeId); - -#if 0 - // Remove 'label' - label.ForgetAllAttributes(); - label.Nullify(); -#endif + if (this->containsLabel(label) && this->findEntity(label) == 0) { + const TreeNodeId nodeId = m_xcaf.deepBuildAssemblyTree(0, label); + this->signalEntityAdded.send(nodeId); + } +} + +void Document::addEntityTreeNodeSequence(const TDF_LabelSequence& seqLabel) +{ + std::vector vecTreeNodeId; + vecTreeNodeId.reserve(seqLabel.Size()); + for (const TDF_Label& label : seqLabel) { + if (this->containsLabel(label) && this->findEntity(label) == 0) { + const TreeNodeId treeNodeId = m_xcaf.deepBuildAssemblyTree(0, label); + vecTreeNodeId.push_back(treeNodeId); + } + } + + for (TreeNodeId treeNodeId : vecTreeNodeId) + this->signalEntityAdded.send(treeNodeId); } void Document::destroyEntity(TreeNodeId entityTreeNodeId) diff --git a/src/base/document.h b/src/base/document.h index fe3b9efe..8a7ec5f9 100644 --- a/src/base/document.h +++ b/src/base/document.h @@ -65,6 +65,7 @@ class Document : public TDocStd_Document { TDF_Label newEntityShapeLabel(); void addEntityTreeNode(const TDF_Label& label); + void addEntityTreeNodeSequence(const TDF_LabelSequence& seqLabel); void destroyEntity(TreeNodeId entityTreeNodeId); // Signals @@ -89,6 +90,8 @@ class Document : public TDocStd_Document { void initXCaf(); void setIdentifier(Identifier ident) { m_identifier = ident; } + TreeNodeId findEntity(const TDF_Label& label) const; + bool containsLabel(const TDF_Label& label) const; ApplicationPtr m_app; Identifier m_identifier = -1; diff --git a/src/base/io_system.cpp b/src/base/io_system.cpp index 47bfd7ac..166e827f 100644 --- a/src/base/io_system.cpp +++ b/src/base/io_system.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -77,7 +76,7 @@ Format System::probeFormat(const FilePath& filepath) const auto fnMatchFileSuffix = [=](Format format) { for (std::string_view candidate : formatFileSuffixes(format)) { if (candidate.size() == fileSuffix.size() - && std::equal(candidate.cbegin(), candidate.cend(), fileSuffix.cbegin(), fnCharIEqual)) + && std::equal(candidate.cbegin(), candidate.cend(), fileSuffix.cbegin(), fnCharIEqual)) { return true; } @@ -229,7 +228,7 @@ bool System::importInDocument(const Args_ImportInDocument& args) if (args.parametersProvider) { taskData.reader->applyProperties( args.parametersProvider->findReaderParameters(taskData.fileFormat) - ); + ); } if (!taskData.reader->readFile(taskData.filepath, &progress)) @@ -259,7 +258,7 @@ bool System::importInDocument(const Args_ImportInDocument& args) taskData.progress, args.entityPostProcessProgressSize, args.entityPostProcessProgressStep - ); + ); const double subPortionSize = 100. / double(taskData.seqTransferredEntity.Size()); for (const TDF_Label& labelEntity : taskData.seqTransferredEntity) { TaskProgress subProgress(&progress, subPortionSize); @@ -267,8 +266,11 @@ bool System::importInDocument(const Args_ImportInDocument& args) } }; auto fnAddModelTreeEntities = [&](const TaskData& taskData) { - for (const TDF_Label& labelEntity : taskData.seqTransferredEntity) - doc->addEntityTreeNode(labelEntity); + // Need to call Document::addEntityTreeNodeSequence() instead of addEntityTreeNode() in + // for() loop. The former function doesn't interleave update of the model tree and emission + // of "entity added" signal for each entity. This prevents data race to happen on the + // Document's model tree within slots connected to signal(and living in other threads) + doc->addEntityTreeNodeSequence(taskData.seqTransferredEntity); }; if (listFilepath.size() == 1) { // Single file case From 5bf0a9eade006106553a1b078077d5e307fc4a91 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 17 Mar 2023 18:21:25 +0100 Subject: [PATCH 013/101] Add CMake build script Relates to GitHub #53 --- CMakeLists.txt | 527 +++++++++++++++++++++++++ scripts/gen-cmake-opencascade-vars.bat | 44 +++ src/app/version.h.cmake | 12 + 3 files changed, 583 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 scripts/gen-cmake-opencascade-vars.bat create mode 100644 src/app/version.h.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..13d0a6a2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,527 @@ +#**************************************************************************** +#* Copyright (c) 2024, Fougue Ltd. +#* All rights reserved. +#* See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +#**************************************************************************** + +cmake_minimum_required(VERSION 3.10) + +set(Mayo_VersionMajor 0) +set(Mayo_VersionMinor 9) +set(Mayo_VersionPatch 0) +set(Mayo_Version ${Mayo_VersionMajor}.${Mayo_VersionMinor}.${Mayo_VersionPatch}) + +project( + mayo + VERSION ${Mayo_Version} + DESCRIPTION "Mayo - Opensource 3D CAD viewer and converter" + LANGUAGES CXX +) + +set(Mayo_Copyright "Copyright @ 2016-present, Fougue Ltd., All Rights Reserved") +mark_as_advanced(FORCE Mayo_Copyright) + +# Find git commit hash and revision number +set(Mayo_VersionCommit 0) +set(Mayo_VersionRevNum 0) + +find_package(Git) +if (Git_FOUND) + # Maybe use preferrably add_custom_command() with PRE_BUILD event? + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE Mayo_VersionCommit + ) + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE Mayo_VersionRevNum + ) + # Fix newline character at the end of Mayo_VersionCommit and Mayo_VersionRevNum variables + string(REGEX REPLACE "\n$" "" Mayo_VersionCommit "${Mayo_VersionCommit}") + string(REGEX REPLACE "\n$" "" Mayo_VersionRevNum "${Mayo_VersionRevNum}") +endif() + +configure_file(${PROJECT_SOURCE_DIR}/src/app/version.h.cmake app/version.h @ONLY) + +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +# Find bit size of the target machine +math(EXPR Mayo_TargetArchBitSize "8 * ${CMAKE_SIZEOF_VOID_P}") + +########## +# Options +########## + +option(Mayo_OptionBuildTests "Build Mayo testing suite" OFF) +option(Mayo_PluginAssimp "Build plugin to import/export mesh files supported by Assimp" OFF) +option(Mayo_PluginGmio "Build plugin to import/export mesh files supported by gmio" OFF) + +########## +# Qt +########## + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +if(QT_VERSION_MAJOR EQUAL 5) + find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets Test) + if(WIN32) + find_package(Qt5 REQUIRED COMPONENTS WinExtras) + endif() +elseif(QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Test OpenGLWidgets) +endif() + +message(STATUS "Qt version ${QT_VERSION}") +if(QT_VERSION VERSION_LESS 5.14) + message(FATAL_ERROR "Qt >= 5.14 is required but detected version is ${QT_VERSION}") +endif() + +#qt_standard_project_setup() +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +########## +# Compiler config +########## + +set(CMAKE_CXX_STANDARD 17) + +set( + Mayo_CompileDefinitions + QT_DISABLE_DEPRECATED_BEFORE=0x050F00 + QT_IMPLICIT_QFILEINFO_CONSTRUCTION +) +set(Mayo_CompileOptions) + +if(MSVC) + list(APPEND Mayo_CompileDefinitions NOMINMAX) + # Deletion of pointer to incomplete type 'XXXX'; no destructor called + list(APPEND Mayo_CompileOptions /we4150) + if(QT_VERSION_MAJOR EQUAL 6) + list(APPEND Mayo_CompileDefinitions _USE_MATH_DEFINES) + endif() +elseif(APPLE) + list(APPEND Mayo_CompileDefinitions GL_SILENCE_DEPRECATION) +endif() + +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list( + APPEND Mayo_CompileOptions + -Wno-undefined-var-template + -Wno-deprecated-declarations + -Wno-macro-redefined + ) +endif() + +if (Mayo_TargetArchBitSize STREQUAL "32") + if(APPLE) + list(APPEND Mayo_CompileDefinitions _DARWIN_USE_64_BIT_INODE) + elseif(UNIX) + list(APPEND Mayo_CompileDefinitions _FILE_OFFSET_BITS=64 _LARGEFILE64_SOURCE=1) + endif() + + if (CMAKE_SYSTEM_PROCESSOR MATCHES "arm") + # See: + # https://stackoverflow.com/questions/48149323/what-does-the-gcc-warning-project-parameter-passing-for-x-changed-in-gcc-7-1-m + # https://stackoverflow.com/questions/52020305/what-exactly-does-gccs-wpsabi-option-do-what-are-the-implications-of-supressi + list(APPEND Mayo_CompileOptions -Wno-psabi) + endif() +endif() + +########## +# Linker config +########## + +set(Mayo_LinkLibraries) +set(Mayo_LinkDirectories) + +if(QT_VERSION_MAJOR EQUAL 5) + list(APPEND Mayo_LinkLibraries Qt5::Core Qt5::Gui Qt5::Widgets) + if(WIN32) + list(APPEND Mayo_LinkLibraries Qt5::WinExtras) + endif() +elseif(QT_VERSION_MAJOR EQUAL 6) + list(APPEND Mayo_LinkLibraries Qt6::Core Qt6::Gui Qt6::Widgets Qt6::OpenGLWidgets) +endif() + +if(MSVC) + list(APPEND Mayo_LinkLibraries Opengl32 User32) +elseif(APPLE) + list(APPEND Mayo_LinkLibraries iconv) +endif() + +########## +# Set "include" directories +########## + +set( + Mayo_IncludeDirectories + src/app + src/3rdparty + ${CMAKE_BINARY_DIR}/app +) + +########## +# Source files +########## + +file( + GLOB Mayo_SourceFiles + ${PROJECT_SOURCE_DIR}/src/app/*.cpp + ${PROJECT_SOURCE_DIR}/src/base/*.cpp + ${PROJECT_SOURCE_DIR}/src/graphics/*.cpp + ${PROJECT_SOURCE_DIR}/src/gui/*.cpp + ${PROJECT_SOURCE_DIR}/src/io_dxf/*.cpp + ${PROJECT_SOURCE_DIR}/src/io_image/*.cpp + ${PROJECT_SOURCE_DIR}/src/io_occ/*.cpp + ${PROJECT_SOURCE_DIR}/src/io_off/*.cpp + ${PROJECT_SOURCE_DIR}/src/io_ply/*.cpp + ${PROJECT_SOURCE_DIR}/src/measure/*.cpp + ${PROJECT_SOURCE_DIR}/src/3rdparty/fmt/src/format.cc +) + +if(WIN32 AND ${QT_VERSION_MAJOR} EQUAL 5) + list(APPEND Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.cpp) +endif() + +if(MSVC) + set(Mayo_RcIconsWin ${PROJECT_SOURCE_DIR}/images/appicon.ico) +endif() + +########## +# Header files +########## + +file( + GLOB Mayo_HeaderFiles + ${PROJECT_SOURCE_DIR}/src/app/*.h + ${PROJECT_SOURCE_DIR}/src/base/*.h + ${PROJECT_SOURCE_DIR}/src/graphics/*.h + ${PROJECT_SOURCE_DIR}/src/gui/*.h + ${PROJECT_SOURCE_DIR}/src/io_dxf/*.h + ${PROJECT_SOURCE_DIR}/src/io_gmio/*.h + ${PROJECT_SOURCE_DIR}/src/io_image/*.h + ${PROJECT_SOURCE_DIR}/src/io_occ/*.h + ${PROJECT_SOURCE_DIR}/src/io_off/*.h + ${PROJECT_SOURCE_DIR}/src/io_ply/*.h + ${PROJECT_SOURCE_DIR}/src/io_measure/*.h +) + +if(WIN32 AND ${QT_VERSION_MAJOR} EQUAL 5) + list(APPEND Mayo_HeaderFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.h) +endif() + +########## +# UI files +########## + +file( + GLOB Mayo_QtUiFiles + ${PROJECT_SOURCE_DIR}/src/app/*.ui +) + +########## +# Unit tests +########## + +if(Mayo_OptionBuildTests) + list( + APPEND Mayo_HeaderFiles + ${PROJECT_SOURCE_DIR}/tests/test_app.h + ${PROJECT_SOURCE_DIR}/tests/test_base.h + ${PROJECT_SOURCE_DIR}/tests/test_measure.h + ) + + list( + APPEND Mayo_SourceFiles + ${PROJECT_SOURCE_DIR}/tests/runtests.cpp + ${PROJECT_SOURCE_DIR}/tests/test_app.cpp + ${PROJECT_SOURCE_DIR}/tests/test_base.cpp + ${PROJECT_SOURCE_DIR}/tests/test_measure.cpp + ) + + list(APPEND Mayo_CompileDefinitions MAYO_WITH_TESTS) + list(APPEND Mayo_LinkLibraries Qt${QT_VERSION_MAJOR}::Test) + + file( + GLOB Mayo_InputTestFiles + ${PROJECT_SOURCE_DIR}/tests/inputs/*.* + ) + file( + COPY ${Mayo_InputTestFiles} + DESTINATION ${CMAKE_BINARY_DIR}/tests/inputs + ) + + file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/tests/outputs) +endif() + +########## +# OpenCascade +########## + +if(WIN32) + # Can't use find_package(OpenCASCADE) as OpenCASCADEConfig.cmake is broken for Windows platform + # See https://dev.opencascade.org/content/3rdparty-hard-coded-paths-when-building-against-opencascade + + set(OpenCASCADE_DIR CACHE PATH "Path where is located OpenCASCADE env.bat file") + execute_process( + COMMAND cmd /c ${PROJECT_SOURCE_DIR}/scripts/gen-cmake-opencascade-vars.bat "${OpenCASCADE_DIR}" + OUTPUT_FILE Mayo_OpenCascadeEnv.cmake + ) + + include(${CMAKE_BINARY_DIR}/Mayo_OpenCascadeEnv.cmake) + if(OpenCASCADE_FOUND) + # Set variables for OpenCASCADE version + file(READ ${OpenCASCADE_INCLUDE_DIR}/Standard_Version.hxx OpenCascade_FileVersionHxx) + string(REGEX MATCH "define[ \t]+OCC_VERSION_MAJOR[ \t]+([0-9]+)" OpenCASCADE_MAJOR_VERSION "${OpenCascade_FileVersionHxx}") + set(OpenCASCADE_MAJOR_VERSION ${CMAKE_MATCH_1}) + string(REGEX MATCH "define[ \t]+OCC_VERSION_MINOR[ \t]+([0-9]+)" OpenCASCADE_MINOR_VERSION "${OpenCascade_FileVersionHxx}") + set(OpenCASCADE_MINOR_VERSION ${CMAKE_MATCH_1}) + string(REGEX MATCH "define[ \t]+OCC_VERSION_MAINTENANCE[ \t]+([0-9]+)" OpenCASCADE_MAINTENANCE_VERSION "${OpenCascade_FileVersionHxx}") + set(OpenCASCADE_MAINTENANCE_VERSION ${CMAKE_MATCH_1}) + set(OpenCASCADE_VERSION "${OpenCASCADE_MAJOR_VERSION}.${OpenCASCADE_MINOR_VERSION}.${OpenCASCADE_MAINTENANCE_VERSION}") + + # Add OpenCASCADE include directory + list(APPEND Mayo_IncludeDirectories ${OpenCASCADE_INCLUDE_DIR}) + + # Set OpenCASACADE_3RDPARTY_BINARY_DIRS variable that will contain the OpenCASCADE + # 3rd-party binary directories(ie containing DLL files) + set(OpenCASACADE_3RDPARTY_BINARY_DIRS) + if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) + list( + APPEND OpenCASACADE_3RDPARTY_BINARY_DIRS + ${TCL_DIR} + ${TK_DIR} + ${FREETYPE_DIR} + ${FREEIMAGE_DIR} + ${FFMPEG_DIR} + ${TBB_DIR} + ${OPENVR_DIR} + ${JEMALLOC_DIR} + ) + else() + if(Mayo_TargetArchBitSize STREQUAL "32") + list(APPEND OpenCASACADE_3RDPARTY_BINARY_DIRS ${CSF_OPT_BIN32}) + else() + list(APPEND OpenCASACADE_3RDPARTY_BINARY_DIRS ${CSF_OPT_BIN64}) + endif() + + # Remove Qt binary path probably added in CSF_OPT_BIN(32/64) variable + set(OpenCASCADE_QT_BINDIR) + foreach(BINDIR ${OpenCASACADE_3RDPARTY_BINARY_DIRS}) + if(BINDIR MATCHES "qt5") + set(OpenCASCADE_QT_BINDIR ${BINDIR}) + endif() + endforeach() + list(REMOVE_ITEM OpenCASACADE_3RDPARTY_BINARY_DIRS ${OpenCASCADE_QT_BINDIR}) + endif() + + # Add OpenCASCADE library directories + # Note: QtCreator option Run->Add_build_library_search_to_PATH will add to PATH env variable + # the contents of Mayo_LinkDirectories variable. For convenience, let's add also the + # directories containing the DLLs + list( + APPEND Mayo_LinkDirectories + ${OpenCASCADE_BINARY_DIR} + ${OpenCASCADE_LIBRARY_DIR} + ${OpenCASACADE_3RDPARTY_BINARY_DIRS} + ) + else() + message(WARNING "OpenCASCADE not found from this location OpenCASCADE_DIR='${OpenCASCADE_DIR}' ") + endif() +else() + find_package(OpenCASCADE REQUIRED) +endif() + +list(APPEND Mayo_CompileDefinitions OCCT_HANDLE_NOCAST) +if(UNIX AND NOT APPLE) + list(APPEND Mayo_CompileDefinitions OCC_CONVERT_SIGNALS) +endif() + +if(OpenCASCADE_FOUND) + message(STATUS "OpenCascade version ${OpenCASCADE_VERSION}") + + # Silent warnings C4996 and STL4015: the std::iterator class template(used as a base class to + # provide typedefs) is deprecated in C++17 + if(MSVC AND OpenCASCADE_VERSION VERSION_LESS 7.7.0) + list(APPEND Mayo_CompileDefinitions _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING) + endif() + + # Add OpenCASCADE libraries + list( + APPEND Mayo_LinkLibraries + # FoundationClasses + TKernel TKMath + # ModelingData + TKG2d TKG3d TKGeomBase TKBRep + # ModelingAlgorithms + TKBO TKBool TKGeomAlgo TKHLR TKMesh TKPrim TKShHealing TKTopAlgo + # Visualization + TKMeshVS TKOpenGl TKService TKV3d + # ApplicationFramework + TKBin TKBinL TKBinXCAF TKCAF TKCDF TKLCAF TKVCAF TKXml TKXmlL + # DataExchange + TKXCAF TKXmlXCAF TKXSBase + ) + + if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) + list(APPEND Mayo_LinkLibraries TKDEIGES) + list(APPEND Mayo_LinkLibraries TKDESTEP) + list(APPEND Mayo_LinkLibraries TKDESTL) + list(APPEND Mayo_LinkLibraries TKDEVRML) + else() + list(APPEND Mayo_LinkLibraries TKIGES TKXDEIGES) + list(APPEND Mayo_LinkLibraries TKSTEP TKSTEP209 TKSTEPAttr TKSTEPBase TKXDESTEP) + list(APPEND Mayo_LinkLibraries TKSTL) + list(APPEND Mayo_LinkLibraries TKVRML) + if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.7.0) + list(APPEND Mayo_LinkLibraries TKXDE) + endif() + endif() + + # OBJ/glTF support + if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.4.0) + list(APPEND Mayo_LinkLibraries TKRWMesh) + if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) + list(APPEND Mayo_LinkLibraries TKDEOBJ TKDEGLTF) + endif() + else() + list( + REMOVE_ITEM Mayo_SourceFiles + ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_base_mesh.cpp + ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_gltf_reader.cpp + ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_obj_reader.cpp + ) + message(WARNING "glTF reader disabled because OpenCascade < v7.4") + message(WARNING "OBJ reader disabled because OpenCascade < v7.4") + endif() + + if(OpenCASCADE_VERSION VERSION_LESS 7.5.0) + list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_gltf_writer.cpp) + message(WARNING "glTF writer disabled because OpenCascade < v7.5") + endif() + + if(OpenCASCADE_VERSION VERSION_LESS 7.6.0) + list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_obj_writer.cpp) + message(WARNING "OBJ writer disabled because OpenCascade < v7.6") + endif() + + # VRML support + if(OpenCASCADE_VERSION VERSION_LESS 7.7.0) + list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_vrml_reader.cpp) + message(WARNING "VRML reader disabled because OpenCascade < v7.7") + endif() +endif() + +########## +# Assimp plugin +########## + +if(Mayo_PluginAssimp) + if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.5.0) + find_package(assimp) + if(NOT assimp_FOUND) + message(WARNING "Assimp library not found") + endif() + else() + message(WARNING "Assimp reader disabled because OpenCascade < v7.5") + endif() +endif() + +if(assimp_FOUND) + message(STATUS "assimp version ${assimp_VERSION}") + list( + APPEND Mayo_HeaderFiles + ${PROJECT_SOURCE_DIR}/src/io_assimp/io_assimp.h + ${PROJECT_SOURCE_DIR}/src/io_assimp/io_assimp_reader.h + ) + list( + APPEND Mayo_SourceFiles + ${PROJECT_SOURCE_DIR}/src/io_assimp/io_assimp.cpp + ${PROJECT_SOURCE_DIR}/src/io_assimp/io_assimp_reader.cpp + ) + list(APPEND Mayo_LinkLibraries ${ASSIMP_LIBRARIES}) + list(APPEND Mayo_CompileDefinitions HAVE_ASSIMP) + + list(GET ASSIMP_INCLUDE_DIRS 0 assimp_IncludeDir) + file(READ "${assimp_IncludeDir}/assimp/version.h" assimp_FileVersionH) + if(NOT assimp_FileVersionH MATCHES "aiGetVersionPatch") + list(APPEND Mayo_CompileDefinitions NO_ASSIMP_aiGetVersionPatch) + message(WARNING "Assimp function aiGetVersionPatch() not available") + endif() +endif() + +########## +# gmio +########## + +# TODO + + +########## +# Targets +########## + +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(Mayo_AppIconMacOS images/appicon.icns) + set_source_files_properties(images/appicon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") +endif() + +add_executable( + mayo + ${Mayo_SourceFiles} + ${Mayo_HeaderFiles} + ${Mayo_QtUiFiles} + ${Mayo_QtI18nFiles} + ${Mayo_QtQmFiles} + mayo.qrc + ${Mayo_RcIconsWin} + ${Mayo_AppIconMacOS} +) + +target_include_directories( + mayo PRIVATE ${Mayo_IncludeDirectories} +) + +target_compile_definitions( + mayo PRIVATE ${Mayo_CompileDefinitions} +) + +target_compile_options( + mayo PRIVATE ${Mayo_CompileOptions} +) + +target_link_directories( + mayo PRIVATE ${Mayo_LinkDirectories} +) + +target_link_libraries( + mayo PRIVATE ${Mayo_LinkLibraries} +) + +add_custom_target( + OtherFiles SOURCES + .github/workflows/ci_linux.yml + .github/workflows/ci_linux_arm.yml + .github/workflows/ci_macos.yml + .github/workflows/ci_windows.yml + images/credits.txt + scripts/bump-version.rb + scripts/gen-cmake-opencascade-vars.bat + src/app/version.h.cmake + README.md +) + +set_target_properties( + mayo + PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "mayo" + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE_COPYRIGHT ${Mayo_Copyright} + MACOSX_BUNDLE_ICON_FILE appicon.icns + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) diff --git a/scripts/gen-cmake-opencascade-vars.bat b/scripts/gen-cmake-opencascade-vars.bat new file mode 100644 index 00000000..670bd54a --- /dev/null +++ b/scripts/gen-cmake-opencascade-vars.bat @@ -0,0 +1,44 @@ +@echo off + +set _OPENCASCADE_ROOTDIR_=%1 + +if not exist "%_OPENCASCADE_ROOTDIR_%\env.bat" echo set(OpenCASCADE_FOUND false) +if not exist "%_OPENCASCADE_ROOTDIR_%\env.bat" exit /b 1 + +set CSF_OCCTIncludePath= +set CSF_OCCTBinPath= +set CSF_OCCTLibPath= +set CSF_OCCTResourcePath= +set TCL_DIR= +set TK_DIR= +set FREETYPE_DIR= +set FREEIMAGE_DIR= +set FFMPEG_DIR= +set TBB_DIR= +set OPENVR_DIR= +set JEMALLOC_DIR= + + +call "%_OPENCASCADE_ROOTDIR_%\env.bat" + + +if not [%CSF_OCCTIncludePath%]==[] echo set(OpenCASCADE_FOUND true) + +echo set(OpenCASCADE_INCLUDE_DIR "%CSF_OCCTIncludePath:\=/%") +echo set(OpenCASCADE_BINARY_DIR "%CSF_OCCTBinPath:\=/%") +echo set(OpenCASCADE_LIBRARY_DIR "%CSF_OCCTLibPath:\=/%") +echo set(OpenCASCADE_RESOURCE_DIR "%CSF_OCCTResourcePath:\=/%") +echo set(TCL_DIR "%TCL_DIR:\=/%") +echo set(TK_DIR "%TK_DIR:\=/%") +echo set(FREETYPE_DIR "%FREETYPE_DIR:\=/%") +echo set(FREEIMAGE_DIR "%FREEIMAGE_DIR:\=/%") +echo set(FFMPEG_DIR "%FFMPEG_DIR:\=/%") +echo set(TBB_DIR "%TBB_DIR:\=/%") +echo set(OPENVR_DIR "%OPENVR_DIR:\=/%") +echo set(JEMALLOC_DIR "%JEMALLOC_DIR:\=/%") +echo set(CSF_OPT_BIN32 "%CSF_OPT_BIN32:\=/%") +echo set(CSF_OPT_BIN32D "%CSF_OPT_BIN32D:\=/%") +echo set(CSF_OPT_BIN64 "%CSF_OPT_BIN64:\=/%") +echo set(CSF_OPT_BIN64D "%CSF_OPT_BIN64D:\=/%") + +REM TODO What about variables CSF_SHMessage, CSF_MDTVTexturesDirectory, ... diff --git a/src/app/version.h.cmake b/src/app/version.h.cmake new file mode 100644 index 00000000..772191de --- /dev/null +++ b/src/app/version.h.cmake @@ -0,0 +1,12 @@ +#pragma once + +namespace Mayo { + +const char strVersion[] = "@Mayo_Version@"; +const char strVersionCommitId[] = "@Mayo_VersionCommit@"; +const unsigned versionRevisionNumber = @Mayo_VersionRevNum@; +const unsigned versionMajor = @Mayo_VersionMajor@; +const unsigned versionMinor = @Mayo_VersionMinor@; +const unsigned versionPatch = @Mayo_VersionPatch@; + +} // namespace Mayo From 0492815444ca8c96c73805e1b752853cc09cc886 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 16 Feb 2024 13:21:04 +0100 Subject: [PATCH 014/101] CI: use CMake instead of QMake Relates to GitHub #53 --- .github/workflows/ci_linux.yml | 39 +++++++++--------------------- .github/workflows/ci_linux_arm.yml | 35 +++++++++++---------------- .github/workflows/ci_macos.yml | 27 ++++++--------------- .github/workflows/ci_windows.yml | 35 +++++++++------------------ CMakeLists.txt | 34 +++++++++++++++++--------- 5 files changed, 66 insertions(+), 104 deletions(-) diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 9703dc99..eb06de52 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -28,42 +28,25 @@ jobs: - name: Install OpenCascade run: | - sudo apt-get -y install libocct-data-exchange-dev libocct-draw-dev - GH_CASCADE_INC_DIR=`dpkg -L libocct-foundation-dev | grep -i "Standard_Version.hxx" | sed "s/\/Standard_Version.hxx//i"` - GH_CASCADE_LIB_DIR=`dpkg -L libocct-foundation-dev | grep -i "libTKernel.so" | sed "s/\/libTKernel.so//i"` - echo "GH_CASCADE_INC_DIR=$GH_CASCADE_INC_DIR" >> $GITHUB_ENV - echo "GH_CASCADE_LIB_DIR=$GH_CASCADE_LIB_DIR" >> $GITHUB_ENV + sudo apt-get -y install libocct-data-exchange-dev libocct-draw-dev occt-misc + sudo apt-get -y install libtbb2-dev + sudo apt-get -y install libxi-dev - name: Install Assimp run: | sudo apt-get -y install libassimp-dev - GH_ASSIMP_INC_DIR=`dpkg -L libassimp-dev | grep -i "version.h" | sed "s/\/version.h//i"` - GH_ASSIMP_LIB_DIR=`dpkg -L libassimp-dev | grep -i "libassimp.so" | sed "s/\/libassimp.so//i"` - echo "GH_ASSIMP_INC_DIR=$GH_ASSIMP_INC_DIR" >> $GITHUB_ENV - echo "GH_ASSIMP_LIB_DIR=$GH_ASSIMP_LIB_DIR" >> $GITHUB_ENV - - - name: Get count of CPU cores - uses: SimenB/github-actions-cpu-cores@v1 - id: cpu-cores - name: Build run: | mkdir ${{github.workspace}}/build cd ${{github.workspace}}/build - echo CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} - echo CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} - echo ASSIMP_INC_DIR=${{env.GH_ASSIMP_INC_DIR}} - echo ASSIMP_LIB_DIR=${{env.GH_ASSIMP_LIB_DIR}} - [ ! -d $CASCADE_INC_DIR ] && echo "ERROR: OpenCascade include dir doesn't exist" - [ ! -d $CASCADE_LIB_DIR ] && echo "ERROR: OpenCascade lib dir doesn't exist" - [ ! -d $ASSIMP_INC_DIR ] && echo "ERROR: assimp include dir doesn't exist" - [ ! -d $ASSIMP_LIB_DIR ] && echo "ERROR: assimp lib dir doesn't exist" - qmake .. CONFIG+=withtests \ - CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} \ - CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} \ - ASSIMP_INC_DIR=${{env.GH_ASSIMP_INC_DIR}} \ - ASSIMP_LIB_DIR=${{env.GH_ASSIMP_LIB_DIR}} - make -j${{steps.cpu-cores.outputs.count}} + cmake --version + cmake .. \ + -DMayo_BuildTests=ON \ + -DMayo_BuildPluginAssimp=ON + cmake --build . \ + --config Release \ + --parallel `nproc` - name: Execute Unit Tests working-directory: ${{github.workspace}}/build @@ -71,7 +54,7 @@ jobs: DISPLAY: :0 run: | # Install french locale for testing case - echo "fr_FR ISO-8859-1" | sudo tee -a /etc/locale.gen + echo "fr_FR ISO-8859-15" | sudo tee -a /etc/locale.gen sudo locale-gen localectl list-locales # Start X11 display server diff --git a/.github/workflows/ci_linux_arm.yml b/.github/workflows/ci_linux_arm.yml index ff73e7b8..0e03849b 100644 --- a/.github/workflows/ci_linux_arm.yml +++ b/.github/workflows/ci_linux_arm.yml @@ -26,11 +26,13 @@ jobs: install: | apt-get -y update apt-get -y install xvfb # Needed to run unit tests + apt-get -y install cmake apt-get -y install git apt-get -y install g++ apt-get -y install qtbase5-dev libqt5svg5-dev apt-get -y install libxcb-cursor0 # Dependencies for "xcb" Qt plugin(actually needed for Qt6) - apt-get -y install libocct-data-exchange-dev libocct-draw-dev + apt-get -y install libocct-data-exchange-dev libocct-draw-dev occt-misc + apt-get -y install libtbb2-dev libxi-dev # Dependencies for OpenCascade v7.5 apt-get -y install libassimp-dev env: | @@ -39,33 +41,24 @@ jobs: RUN_ARCH_NAME: ${{matrix.arch}} run: | - GH_CASCADE_INC_DIR=`dpkg -L libocct-foundation-dev | grep -i "Standard_Version.hxx" | sed "s/\/Standard_Version.hxx//i"` - GH_CASCADE_LIB_DIR=`dpkg -L libocct-foundation-dev | grep -i "libTKernel.so" | sed "s/\/libTKernel.so//i"` - GH_ASSIMP_INC_DIR=`dpkg -L libassimp-dev | grep -i "version.h" | sed "s/\/version.h//i"` - GH_ASSIMP_LIB_DIR=`dpkg -L libassimp-dev | grep -i "libassimp.so" | sed "s/\/libassimp.so//i"` git clone https://github.com/fougue/mayo.git cd mayo echo GIT_BRANCH_NAME=$GIT_BRANCH_NAME + # Fixes "fatal: detected dubious ownership in repository at '/home/runner/work/mayo/mayo'" + git config --global --add safe.directory `pwd` + git checkout $GIT_BRANCH_NAME cd .. mkdir build-mayo cd build-mayo - echo GH_CASCADE_INC_DIR=$GH_CASCADE_INC_DIR - echo GH_CASCADE_LIB_DIR=$GH_CASCADE_LIB_DIR - echo GH_ASSIMP_INC_DIR=$GH_ASSIMP_INC_DIR - echo GH_ASSIMP_LIB_DIR=$GH_ASSIMP_LIB_DIR - [ ! -d $GH_CASCADE_INC_DIR ] && echo "ERROR: OpenCascade include dir doesn't exist" - [ ! -d $GH_CASCADE_LIB_DIR ] && echo "ERROR: OpenCascade lib dir doesn't exist" - [ ! -d $GH_ASSIMP_INC_DIR ] && echo "ERROR: assimp include dir doesn't exist" - [ ! -d $GH_ASSIMP_LIB_DIR ] && echo "ERROR: assimp lib dir doesn't exist" - qmake ../mayo \ - CONFIG+=debug \ - CONFIG+=withtests \ - CASCADE_INC_DIR=$GH_CASCADE_INC_DIR \ - CASCADE_LIB_DIR=$GH_CASCADE_LIB_DIR \ - ASSIMP_INC_DIR=$GH_ASSIMP_INC_DIR \ - ASSIMP_LIB_DIR=$GH_ASSIMP_LIB_DIR - make -j`nproc` + cmake --version + cmake .. \ + -DMayo_BuildTests=ON \ + -DMayo_BuildPluginAssimp=ON + cmake --build . \ + --config Release \ + --parallel `nproc` + # # Execute Unit Tests # Note: for some unknown reason unit tests are crashing for armv7(on very first test) diff --git a/.github/workflows/ci_macos.yml b/.github/workflows/ci_macos.yml index 8161795a..7234961a 100644 --- a/.github/workflows/ci_macos.yml +++ b/.github/workflows/ci_macos.yml @@ -19,16 +19,10 @@ jobs: - name: Install OpenCascade run: | brew install opencascade - GH_CASCADE_BASE_DIR=`brew info opencascade | grep -E -i --only-matching --max-count=1 "^(/[a-z\.\-_0-9]+)+"` - echo "GH_CASCADE_INC_DIR=$GH_CASCADE_BASE_DIR/include/opencascade" >> $GITHUB_ENV - echo "GH_CASCADE_LIB_DIR=$GH_CASCADE_BASE_DIR/lib" >> $GITHUB_ENV - name: Install Assimp run: | brew install assimp - GH_ASSIMP_BASE_DIR=`brew info assimp | grep -E -i --only-matching --max-count=1 "^(/[a-z\.\-_0-9]+)+"` - echo "GH_ASSIMP_INC_DIR=$GH_ASSIMP_BASE_DIR/include/assimp" >> $GITHUB_ENV - echo "GH_ASSIMP_LIB_DIR=$GH_ASSIMP_BASE_DIR/lib" >> $GITHUB_ENV - name: Get count of CPU cores uses: SimenB/github-actions-cpu-cores@v1 @@ -38,20 +32,13 @@ jobs: run: | mkdir ${{github.workspace}}/build cd ${{github.workspace}}/build - echo CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} - echo CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} - echo ASSIMP_INC_DIR=${{env.GH_ASSIMP_INC_DIR}} - echo ASSIMP_LIB_DIR=${{env.GH_ASSIMP_LIB_DIR}} - [ ! -d $CASCADE_INC_DIR ] && echo "ERROR: OpenCascade include dir doesn't exist" - [ ! -d $CASCADE_LIB_DIR ] && echo "ERROR: OpenCascade lib dir doesn't exist" - [ ! -d $ASSIMP_INC_DIR ] && echo "ERROR: assimp include dir doesn't exist" - [ ! -d $ASSIMP_LIB_DIR ] && echo "ERROR: assimp lib dir doesn't exist" - qmake .. CONFIG+=withtests \ - CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} \ - CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} \ - ASSIMP_INC_DIR=${{env.GH_ASSIMP_INC_DIR}} \ - ASSIMP_LIB_DIR=${{env.GH_ASSIMP_LIB_DIR}} - make -j${{steps.cpu-cores.outputs.count}} + cmake --version + cmake .. \ + -DMayo_BuildTests=ON \ + -DMayo_BuildPluginAssimp=ON + cmake --build . \ + --config Release \ + --parallel ${{steps.cpu-cores.outputs.count}} - name: Execute Unit Tests working-directory: ${{github.workspace}}/build diff --git a/.github/workflows/ci_windows.yml b/.github/workflows/ci_windows.yml index 4b4306b4..42c97700 100644 --- a/.github/workflows/ci_windows.yml +++ b/.github/workflows/ci_windows.yml @@ -66,38 +66,27 @@ jobs: - name: Extract Assimp run: 7z x assimp-5.3.1.zip - - name: Download jom.exe - uses: carlosperate/download-file-action@v2 - with: - file-url: 'https://www.fougue.pro/share/bin/jom.exe' - sha256: '700943516856c15675212bbcecc4e3d7580514456f8b6f09d634e98e25296ad4' - - name: Get count of CPU cores uses: SimenB/github-actions-cpu-cores@v1 id: cpu-cores - - name: Create Build folder - run: mkdir ${{github.workspace}}/build - - name: Configure Compiler uses: ilammy/msvc-dev-cmd@v1 - - name: QMake - working-directory: ${{github.workspace}}/build + - name: Build shell: cmd run: | - call ..\OpenCASCADE-${{matrix.occ_version}}-vc14-64\opencascade-${{matrix.occ_version}}\env.bat - echo CSF_OCCTIncludePath=%CSF_OCCTIncludePath% - echo CSF_OCCTLibPath=%CSF_OCCTLibPath% - qmake --version - qmake ..\mayo.pro CONFIG+=release CONFIG+=withtests ^ - ASSIMP_INC_DIR=${{github.workspace}}/assimp-5.3.1/include/assimp ^ - ASSIMP_LIB_DIR=${{github.workspace}}/assimp-5.3.1/lib ^ - ASSIMP_LIBNAME_SUFFIX=-vc143-mt - - - name: Build - working-directory: ${{github.workspace}}/build - run: ..\jom.exe -j${{steps.cpu-cores.outputs.count}} + mkdir ${{github.workspace}}\build + cd ${{github.workspace}}\build + cmake --version + cmake .. ^ + -DMayo_BuildTests=ON ^ + -DMayo_BuildPluginAssimp=ON ^ + -DOpenCASCADE_DIR=../OpenCASCADE-${{matrix.occ_version}}-vc14-64/opencascade-${{matrix.occ_version}} ^ + -Dassimp_DIR=${{github.workspace}}/assimp-5.3.1/lib/cmake/assimp-5.3 + cmake --build . ^ + --config Release ^ + --parallel ${{steps.cpu-cores.outputs.count}} - name: Execute Unit Tests working-directory: ${{github.workspace}}/build diff --git a/CMakeLists.txt b/CMakeLists.txt index 13d0a6a2..a62dc616 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,9 +54,9 @@ math(EXPR Mayo_TargetArchBitSize "8 * ${CMAKE_SIZEOF_VOID_P}") # Options ########## -option(Mayo_OptionBuildTests "Build Mayo testing suite" OFF) -option(Mayo_PluginAssimp "Build plugin to import/export mesh files supported by Assimp" OFF) -option(Mayo_PluginGmio "Build plugin to import/export mesh files supported by gmio" OFF) +option(Mayo_BuildTests "Build Mayo testing suite" OFF) +option(Mayo_BuildPluginAssimp "Build plugin to import/export mesh files supported by Assimp" OFF) +option(Mayo_BuildPluginGmio "Build plugin to import/export mesh files supported by gmio" OFF) ########## # Qt @@ -226,7 +226,7 @@ file( # Unit tests ########## -if(Mayo_OptionBuildTests) +if(Mayo_BuildTests) list( APPEND Mayo_HeaderFiles ${PROJECT_SOURCE_DIR}/tests/test_app.h @@ -333,6 +333,16 @@ if(WIN32) endif() else() find_package(OpenCASCADE REQUIRED) + if(OpenCASCADE_FOUND) + # It seems that OpenCascade cmake package files specify INTERFACE_INCLUDE_DIRECTORIES + # starting from 7.7.0 version only + # Older versions require to explicitly add OpenCascade include dir + get_target_property(OpenCASCADE_TKernel_INCLUDE_DIR TKernel INTERFACE_INCLUDE_DIRECTORIES) + list(FIND OpenCASCADE_TKernel_INCLUDE_DIR ${OpenCASCADE_INCLUDE_DIR} OpenCASCADE_TKernel_INCLUDE_DIR_IsSet) + if(OpenCASCADE_TKernel_INCLUDE_DIR_IsSet EQUAL -1) + list(APPEND Mayo_IncludeDirectories ${OpenCASCADE_INCLUDE_DIR}) + endif() + endif() endif() list(APPEND Mayo_CompileDefinitions OCCT_HANDLE_NOCAST) @@ -394,24 +404,24 @@ if(OpenCASCADE_FOUND) ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_gltf_reader.cpp ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_obj_reader.cpp ) - message(WARNING "glTF reader disabled because OpenCascade < v7.4") - message(WARNING "OBJ reader disabled because OpenCascade < v7.4") + message(STATUS "glTF reader disabled because OpenCascade < v7.4") + message(STATUS "OBJ reader disabled because OpenCascade < v7.4") endif() if(OpenCASCADE_VERSION VERSION_LESS 7.5.0) list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_gltf_writer.cpp) - message(WARNING "glTF writer disabled because OpenCascade < v7.5") + message(STATUS "glTF writer disabled because OpenCascade < v7.5") endif() if(OpenCASCADE_VERSION VERSION_LESS 7.6.0) list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_obj_writer.cpp) - message(WARNING "OBJ writer disabled because OpenCascade < v7.6") + message(STATUS "OBJ writer disabled because OpenCascade < v7.6") endif() # VRML support if(OpenCASCADE_VERSION VERSION_LESS 7.7.0) list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_vrml_reader.cpp) - message(WARNING "VRML reader disabled because OpenCascade < v7.7") + message(STATUS "VRML reader disabled because OpenCascade < v7.7") endif() endif() @@ -419,14 +429,14 @@ endif() # Assimp plugin ########## -if(Mayo_PluginAssimp) +if(Mayo_BuildPluginAssimp) if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.5.0) find_package(assimp) if(NOT assimp_FOUND) message(WARNING "Assimp library not found") endif() else() - message(WARNING "Assimp reader disabled because OpenCascade < v7.5") + message(STATUS "Assimp reader disabled because OpenCascade < v7.5") endif() endif() @@ -449,7 +459,7 @@ if(assimp_FOUND) file(READ "${assimp_IncludeDir}/assimp/version.h" assimp_FileVersionH) if(NOT assimp_FileVersionH MATCHES "aiGetVersionPatch") list(APPEND Mayo_CompileDefinitions NO_ASSIMP_aiGetVersionPatch) - message(WARNING "Assimp function aiGetVersionPatch() not available") + message(STATUS "Assimp function aiGetVersionPatch() not available") endif() endif() From ecf75e028b68a2b0c01e6003985b767fa91e8578 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 22 Feb 2024 16:22:12 +0100 Subject: [PATCH 015/101] CMake: use Windows appicon and qtbase qm files Relates to GitHub #53 --- CMakeLists.txt | 21 ++++++++++----------- i18n/qt5base.qrc | 6 ++++++ i18n/qt5base_en.qm | 1 + i18n/qt5base_fr.qm | Bin 0 -> 166167 bytes i18n/qt6base.qrc | 6 ++++++ i18n/qt6base_en.qm | 1 + i18n/qt6base_fr.qm | Bin 0 -> 159463 bytes images/appicon.rc | 1 + 8 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 i18n/qt5base.qrc create mode 100644 i18n/qt5base_en.qm create mode 100644 i18n/qt5base_fr.qm create mode 100644 i18n/qt6base.qrc create mode 100644 i18n/qt6base_en.qm create mode 100644 i18n/qt6base_fr.qm create mode 100644 images/appicon.rc diff --git a/CMakeLists.txt b/CMakeLists.txt index a62dc616..5afc6de2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ set(Mayo_VersionCommit 0) set(Mayo_VersionRevNum 0) find_package(Git) -if (Git_FOUND) +if(Git_FOUND) # Maybe use preferrably add_custom_command() with PRE_BUILD event? execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD @@ -56,7 +56,8 @@ math(EXPR Mayo_TargetArchBitSize "8 * ${CMAKE_SIZEOF_VOID_P}") option(Mayo_BuildTests "Build Mayo testing suite" OFF) option(Mayo_BuildPluginAssimp "Build plugin to import/export mesh files supported by Assimp" OFF) -option(Mayo_BuildPluginGmio "Build plugin to import/export mesh files supported by gmio" OFF) +# TODO +# option(Mayo_BuildPluginGmio "Build plugin to import/export mesh files supported by gmio" OFF) ########## # Qt @@ -106,7 +107,7 @@ elseif(APPLE) list(APPEND Mayo_CompileDefinitions GL_SILENCE_DEPRECATION) endif() -if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") list( APPEND Mayo_CompileOptions -Wno-undefined-var-template @@ -115,14 +116,14 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") ) endif() -if (Mayo_TargetArchBitSize STREQUAL "32") +if(Mayo_TargetArchBitSize STREQUAL "32") if(APPLE) list(APPEND Mayo_CompileDefinitions _DARWIN_USE_64_BIT_INODE) elseif(UNIX) list(APPEND Mayo_CompileDefinitions _FILE_OFFSET_BITS=64 _LARGEFILE64_SOURCE=1) endif() - if (CMAKE_SYSTEM_PROCESSOR MATCHES "arm") + if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm") # See: # https://stackoverflow.com/questions/48149323/what-does-the-gcc-warning-project-parameter-passing-for-x-changed-in-gcc-7-1-m # https://stackoverflow.com/questions/52020305/what-exactly-does-gccs-wpsabi-option-do-what-are-the-implications-of-supressi @@ -182,12 +183,12 @@ file( ${PROJECT_SOURCE_DIR}/src/3rdparty/fmt/src/format.cc ) -if(WIN32 AND ${QT_VERSION_MAJOR} EQUAL 5) +if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) list(APPEND Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.cpp) endif() if(MSVC) - set(Mayo_RcIconsWin ${PROJECT_SOURCE_DIR}/images/appicon.ico) + set(Mayo_RcIconsWin ${PROJECT_SOURCE_DIR}/images/appicon.rc) endif() ########## @@ -209,7 +210,7 @@ file( ${PROJECT_SOURCE_DIR}/src/io_measure/*.h ) -if(WIN32 AND ${QT_VERSION_MAJOR} EQUAL 5) +if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) list(APPEND Mayo_HeaderFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.h) endif() @@ -484,9 +485,7 @@ add_executable( ${Mayo_SourceFiles} ${Mayo_HeaderFiles} ${Mayo_QtUiFiles} - ${Mayo_QtI18nFiles} - ${Mayo_QtQmFiles} - mayo.qrc + mayo.qrc i18n/qt${QT_VERSION_MAJOR}base.qrc ${Mayo_RcIconsWin} ${Mayo_AppIconMacOS} ) diff --git a/i18n/qt5base.qrc b/i18n/qt5base.qrc new file mode 100644 index 00000000..13681d7a --- /dev/null +++ b/i18n/qt5base.qrc @@ -0,0 +1,6 @@ + + +qt5base_en.qm +qt5base_fr.qm + + diff --git a/i18n/qt5base_en.qm b/i18n/qt5base_en.qm new file mode 100644 index 00000000..be651eed --- /dev/null +++ b/i18n/qt5base_en.qm @@ -0,0 +1 @@ +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 DJ-|-< literal 0 HcmV?d00001 diff --git a/i18n/qt6base.qrc b/i18n/qt6base.qrc new file mode 100644 index 00000000..68b76057 --- /dev/null +++ b/i18n/qt6base.qrc @@ -0,0 +1,6 @@ + + +qt6base_en.qm +qt6base_fr.qm + + diff --git a/i18n/qt6base_en.qm b/i18n/qt6base_en.qm new file mode 100644 index 00000000..be651eed --- /dev/null +++ b/i18n/qt6base_en.qm @@ -0,0 +1 @@ +;ft(RusG6|IF;YTe6$vDewD!zvq#YP4>>5IsKeDGk3_7#Vrqh zciWAxoZa^6S$FOE@IE3kmIP*I<&7jdcMLvtBVCjEfZa)1kx672LG;&?fa!oA0OEUp zcLvM=91n=^{JjZq6wyCrfLVag0geXz9B>RNWCI)vmhhKPKg- zp;+Yk_+AeQd*=wKwDn2Y%?LOFe_sqZk*Lwb0xA!81AK;*z5R(yi}3gR0skiYvlEdS zWBz$FU3v(K~FSpHz4pDhq0QzBA~M7ZKCWb4WJlmb9rj(TTAS=Ch5@k#$NLhUe8SbsYI&T$FS+kf7_dz!9_X((L`U4prT1Cp5&1Beo9#PRbWO#fO=~^!V z{0MaVgADsF!TRlFc+G%u{t-~QaSs_@2aTp21O(mIVcfR}a0?mUzJ}TI_@)=p?xg}MZT3sJ-Xx%|^>1YO1>d`^RKkDkWHh`6ew2~XaTZbO z>tu9gLIz%yaMn69c0WwYrY>Y0GMtqA-vFFUw1+=W#^*l(?;_=ahsZbvve^4q3EeHo zn0*ZVJWa+apnJ!YfSX8Z@d9)jx-or`fJ)n&B-}EPjHO3N8Pb}J<&f*GuaPkTK5Uy! z#+8uUM+Xb2v&hdn>pU{90^L$RCF9NeNon_#gxlwk@ix$H$Jb=M19I~CVlv)kBq+_a zGip48;V5cy{f|Ts>8a`aCy0)}Pi=1j9{tBrHeILjRW!r$H~lz8t)U&Tq1Xbm#S?p}U|DPn6M! zH=2?%_9My|yn*PzZp!lYBYH=X@aPH}y>2b^buHjNq9*Usm~)rHhFEFL{PXbp*J*5L z=yBmf8e4OMl)pC6xb90xxvq-F4Y-kL#Bd3BPZCgRlP=+P-_y9FLQ;m-(D>`Ht|_-r zwjF#}yMeMV1Yg(wLfK34eCw+y`;W_^$FI@^9qi21B{ZS`GK_neCU_nrF zlal%+DzfR-#XDENr45B|iq=ExKL7y&?@E<=-9zKI~L#t@=N3j28SJRZ< zu)pbtXv!kkwe(z?as|f!t%jyty$O0!E}*h?C{0;)BlHWOuloVd?V>5ajDnu&Y3izt zM7Cu#^|}I5dbE_Vf0BfcTuc|d2s%IdK3#BNA1Q4Q(giQy0=@eeT>w0l$A;0geLrEm zZ)n=f&k!}ci>7^ndH3egwC@f>4=2;KU#^3#Hq(XOpTXxXbYUs*c>F`UaOt1WzqT~J zq%SGEE})A{SCcY&H(hiw_`iR$fXWL!>7w7ChJIa4?qGY;^bc zG`s(Lz@0QFcPS|YS5n0a=<^%zlW)diqQN`Ke-QW{A4!1)pwnS1&FeOZ6h|YPw+x@( zanU7=&-*&Mq#X9+=&N)|O(*DIIxRGS{s)5sD*swVOa9zX%GdAF()|ZWnK+h~&AS$U z_D=zIP1@3xhaVwj{e85u1U@vdf>w3SfPENGtNUe>a*lyk_um4TMb{0;fV`fdP17Mi z?Hsgi>~>P_dz+q^8-!i$Ku`RNb)NSsJ-g~0Qi?XyzN5frvyJw*90mK5M+bHwM(BQ_ zgv-`QxY8rxqrXb{Nrr@<7YeA%JtkrGFeejoh2`iRnM zwudNrywb*=L(01!DQyniL-gPsO4}bB!A@^d+6_5Qx-P{^`*->S-z25utV&XPc2GJ^ zE+BfbTIqHi^7G|srTa}sNSXSc(z7T?I>Wt6+IJg>&N^QiQVKf1y;~Wc5+u6hdnNO& z-b9s;D`PgTC0f{388_iHDKnl@W{%%Yw5h8yD?J_l%c!{Xh9idQrg%;R-)DbUJih=A zB&GbyD@mytrIc^zL(0bwOZfSNO8K4m>^V=VxbOh{;1Z?c%%3ZHbC7tso<^Dd6 zpkK?ChnJTUt@}xN`0Gr_`Cw(ocKDZ(1C+<}J|bNom-57W7E&hspgeu-IMKkl$}>ZC zh+qCxo_7?J?(F-N1OGH5W!UA)t0y7Hc{eJbUyj)K`ty`;AKgcEd8zVKb%1EqVdcz~ z%ShStq;lqi-$=RSl=9c?WkdrUI_r}d_p|o8HtS9!e)wD0p&WL->p@+oPKQXjwXv>i z+xCb(x9Pg>eSm1aSJ(5Idx)wEb>|KRU-yjE^?tV_(eqy2kctOL@pjP-zo`$=krdtV zT{)y{bGB~QSIvlerc3zsHeGosKL6m=mDj)@-8fKJz7_$}g{iu^i;u%kp480?!tc3O z3#cp|ty_5B-K0!$>#ptn7x3$*TianT>3S{Dt(^=xYn-H8=l+D07)Q6K3ZXS;siVF7hbUe`U52f6<9Tip|Ezt35r+j$0ZIr16Z?!{@4 zgCBHz&e}_4cj@*n*$#M_fI9O4-QL^&AZ7cHx~K7XWzhlM)1yw1vbdG*8GKJ!?9uJJ zbT#R$nYtH;K0@^B9lH17J8ABNy7!-NMauK<=|0QGcqb}#$L~=f_kZg?pPdYQb(Zd@ zCE#<W&-Lw1LHOVE^{%g$kh0{o{_NL0q-(rc zpZeqm()FLG@A4$#@ZXL4u1DGw_Oxld^xKzPif}-4yO+maombIs6i|KvF0s$Bh3-@=aF zHbTF9TMPKV4f@^x+(-1-7y4%mdc=hn>z{QDKzy)Y|2*^M>C5!{tHJ*VI_VFdhJ8A+ zRsX7U2I*2O^lvVQ{p&hI|Mu00e_Hp@zw^Un(oswOXScpcx*oIj$80{L?=RCI`x*B5 zaYcW;$6=x?Ht4@T@((FzjnSWO0y{kY5d-~}PRcQp!MtH7DOuYMPAi^!_a#HiMz9xm zbTy=o0e`M&W9Ysf{O#DwkY<6NbidYc?xuZ2%kMPw``~O+Zs}|2zpM@NtM3fM`*(%C zdESr_Fu-oDGK^i2Pt?4VA-nZ^q+DNQ$U8BNbVITX(@%vCeKOhIQL>AcrRm z>vkcZS(swDc`V|jHZ}<-bdd1ckpk*keQCI*%cF>k#~SX?k0s@`K3zz{| z1Nbk%t4O)?eZX7ry%vBE0OkWe1$ZssN&LMC@HfCa40mvSX5$xt(@44J96&GNOu(x! z-WfoQv#~GW1Au-&jI;3}KsT~G~Ncgbo(BoX&d6i-=8(wmO#!t*BF~_J^?#(gR%AQB}CWUYi#p7^lr&-#Dc-G_atCRgk*Ya}ky{EBb^J9cW)|k3-G4h~NW7n7Z5Z#$)OuMfJ@zfGy{}ZSW zZCYs@bp1Y3?mf#m=t1c3y%UY+Z`%VrOO3;NgKw!f8Hab+NVNSQW7ccn>pw$`&nxKLGlz_|tfU1#(lCZ(KLX zir-flZyw!=sKRQz<=q`bpByr-UxD~yM~!j){rj-aLgQ`S@4@G-#trQuU!9K|Hx2+@ z#y2%oN+-5um|Das|2WQ{xbf3Hu!bsjLCT72gD--OpXIi*kz-s(eE9Jo@#7ry0{SW z;#U&RdePME`!w+DGEA3DF(Tn$G_EX`&SmnK})J-0r&4)b&UBoAN66)m|~jzQ33RDmT5|tBKY?nrfK_j!OpZZUD&Uj z=$}5O3txT%{%Hf?n}BZvzJ#|I~4uY33w+|GB=VnU#P8GfX9=@1dUA%{2R7UiV$5Ik|fg4<9x8I`%`}{e#K> zKJ>I-8&l;EpzF&sO>@8OfIO$Y=~5Hok(7P{DkCPC7FAydy)&5>%?DgG#RH)|+-r+m5GeK{I{690D=ncE`_iEGEb6}sIn``y_nm3Sl z-f12<2X?w}u6a;%$VanT=D~eo_d9)J9`*+0_t)j-;pcyZ{iK2BjGHH;&hVExv)#ME zXS#V@FW8koCz*xb{j&-XdZBM?p1ct8-L*mUMVlLu()V-o#pec5H$2De4(x|r_{&_< z8}_8d9&`DR#n7L#%)UX8=O6AgFXZp{>t$Xv|3b|Bj(O3o9U;F)^Rl%w;0OOUuhKU| zoHEP2>NVt(qqEJ|UAPADG6^4f+kD3(p!=dd=DUB#x{uyq-uP=r#Lp+p+eV*4edDxw z+bkR6zn11lnhrsn^RapR_#vpNZ;ekoexBca0({-)sKv_hqn?-J;Ks+8M^c{Qm*_&K%MbT%dp;QpyyW0aQvPg93tViQI-+o zK=14Bv1A0tVw}~MQ6-?qnO7}Yzb{8V`HE%icYh*|&b8$1oJ7h)b1gZ?U`O0PTc&jS zmS{l-32QF5Tre5@nfiofn#YYjwvLt=6PhA#{1FiJw+*+ z2|t`^DV$*><*l8TvavTJuG(+$eg(cCJ7B2*8I=d`v@GZiKXdIvmP@+z!9Lcnmc@U= zPQ7!HWy$@;sE7G1S5HPfu<9Pm%FXWqer&l>K|GN0o#nEg|n`feWoM%b#3|2N zb~Sl}be7vCv_4?j-3j*fW}jvE!-tVyuCwe}1-*W>-11Bz^iui2vhQ6l>Y<}7FJ>T~ zX!ncdfC=)kewO9nb}Vey1D1o2A3^>6faT?uKB6Dbvm83GAAWY8wnqeinY>VaSVc6Mr?^@np3%MHLuzbjNw_Rh)XCD-k?wm_4$I@3L?(SnzOg1-CPT06{v-@bIIwd;0#9~nts*LS3x zYq9pd6?h){)H-DR-=Le_I{Fm+&Cpcq^kKIk9|>9uuPY>-?SQp#_bk};3D&~BvqrO7g{^>^R9h+cZ$A4tK4>(xX5G0B zaoy2m>ptu~(se1;{o6c<|39!EoYtL`D~?!SMxBA0d8|j0U?=SJ0HJ^O=dJG?0$r^s z)(`0j^6&4hr&fV~M^8!k{#5I?qxM0sF17y9sWa*9#nv;sAg}kVxBmX*o8a4n);}^# zz;C7XpMAjV_AxfYmva#}&$pQzkehMSY^FcApq}%q&Aezk;@$B!+v}j$%WZ5ar`lpa z_)S~qF3)2xKTE<#TH3l?gnie+SJ}Fjv_d`d1zXo0koO)rwr=O103Lg6-S$I{|5|8E z8wNbLPO$a4?*!_yn{5NDeq0dPqSToJ>aX!wi&6Q%chrYvmP-bU;0BprSdrmgE!d<4bb1snYN-XzW zJ0f5D!gl?1{C&M*yLs$wsJBkDt*^lPI=F1Nwb!Apbb;-*%_&4rE*DU#*=f6@WCu~s zKHD9$e<0USGSIfUQzh!(e%t0A z%klglwk^YcM}6gS+oPX%ME*X__SCQdDWzR(PmMYob+S)vdwlr(*eTl{Km5%09=4}v z8Q|xww&xE+uHUuT_BoHCf8aR@|DI%fq3KBYIkLU5uQBZLFhIz2tGTup=Ybzj-fBCD zRtV*_w*g&<%UatGmZ#wNpW0qt3qF-kw7ov*Nz%3a)b{4o!{9@T?V|yM;Qzh0<2QUk zI>l!@@z7Yn6x)d-htn+HGfhk*~V#wgBjR!%DloE#!8`gwr3w5?b_LRHV zB3^Ub&*>Q?df9C6+;j=@k74%i55dlLeAC|R3fQm4ciD%II2-k{efANIr)7{mef@8w zeDZ)jV!zn7kDGa@W`|#=ad59csV$Gw{F1OZKAYKrh|q5Ikcq8H)OF@0aaf|1i>ZJAXw?e_9@)uinH&|dXPB`N-^?epgjCFP^Z_L>aj6HQa?i*h$%pJ};$nG^DxF~Ppl zat|qGi|wn%VE&2Q?bm$ZfPA&FU-vrbaPznJ>pQ|96cpOmq>VsczR145(Q@#6mi>0! z3e;!I?Hl>~&6-Pi|6u`jDPH@gONvn+IBLItcYlocgZ-gpA7i|&_O0)Y1HUe|Z*RRC z`OFXYr-R_<$)D}d%>n)Gp9`onUuA#pIUVBd@9i(5eoA9+wZAk0d_4bV`@x)HM0cLB zzkDw8foq1@4?Xb}_W#=35AFO3^0(4{xMwEp(klDg?_%7IXYB7i0lwH?upjO87?E?m z{r%Bc=Y4(b@8kDMzn%6E24J3dPTD`i{L128_Rp35=qDIx|Eg;ZDJA*#ZwiJX?mBG$ z=?>`0(lY|;4FA|q?>i6v?sogny|_Lx-$5OrXWKVAboZS^9{Q!j&>wPO_}*bWV=|jeg=Eza7?@n^go>L$eXen{_hmv7Rc{Ij{MQT5gjgd)~&i})f*)-D zlcV~3*xOq!cPv{2ye20*R_whS`Sw+gRc|1kT=~7@nwj~KlT63zFOAp_);q4-Q-FQ( zPbGZpatS~GO2Quujy3D=M*VS^wg?;^?)DLIMxntjdApj zwfn7zv(Iv@yXITO2OS*ii*A9R&UD=NC+5|;9JhCc-n{&%jSxtC*203m-aiJLhqe&NxEZ~ zQiXnrHjWo=e+2d8%N&PJK^=sn?t6f9tR)so&zg$W!_x4QSdA{(4{1pnLJWX?4=z z$9#x`wkDl_U@z&KE>FrTZH#)rqNMTfzz@8!Flkb72jc%JN%`OO0H1awm3)cyFC3oa z&B_3NDM{XU!OsiU3#hF7ENS*zki)coNfqZEM_kb}$#)OvGw}PQYQpcgyp~jb(1!f> z>!j*GpT$1UgGuvJH^Kj$Nt(}pU$`h~{vz1fg-<6foW6y0!(K|doa;${lO8;shyBQ& zNn1Oi-laP)>2V|C+BeTldc5Ziq+EC;XPNt5e9{*S@b{BHCw;vec69o0N#7hnT)X_cr0;|Mk-t2W^lQ7l z&@)5QuLs{mTsFl?M_|8Gb~{b;v99d4&PKOGUX9Zw+;+b+<;wR_$9vS-9FQ(}-Pz(0 z{E+WcXPZ*~-s{fxS zM20L0_aAl+`~h+C@WswSZT?1`_g3eioq+A`bdLM16a3y{XU<39>$(-ri4)f%FIniE z(jRualf`*KUNz|&o#UK-hm~|~r#q)_gPz^c-8t*K1MrLMobI&!@MG^dOL;$Z-%ZXM z3*;?tjk9Jr^zOZTotJkqqTeA!!kRA5-e>v}%4ZGG{@4VyajihrnbKd)GRc)d0L+ z8s&WVt&aHp0_O(-tp9=b&JWl9O-kqP&SQaQh(C`wPqZ%p9o~1I7zHRa`Eh2l?eX`pzmk@0KLvUh97|3q&qTiQOmge-eb5i{ zP4Zb7U)S%?eC(7wbRq0z$JFGZk1j{vIVpMA z90he1Uvk#KeyG=fojj)E3FNTe`v(W+!m zU#!p8C%F_*>AODJi+TX*+{xap1wjwUNng?~IQg#W=zHwBIQhZXz=t85 zlOK8o_H5fA3IExYyd?$n>i<{rR^t<>KYf_I9lhI1^62C}FMo=9&2!0nDTgTg)8uDe zh?i%cPJaHqAkle4lJ~90dV8Id{L-rJMEwKFFKq_w_iplm=8)TRU-E&#Vd%$G0_w~k zCLj1;e||%c`oy=%KfeY4 zqWdoS%mCz-o32RyH3jpw9)Tyrdf*)I*ZG zP$%k5chX>TQ8~HsZxP^0Ko3<>5O5xOshB(#e3?x6uTgG(O;ylSk?*N+SC;t$9y9;4 zQ}4m9Ii4Dqx60+Is0`L9Ja~J`!Y~1<5dZQ7oyku=D#cG83Ti`IGs^u{9#?_8kjJu% zPa~^?LBCH7)0UnA9+eo#kAMBZ%f%SsU*N6{Vo5LbR|j34dh-}56VfwgrcaubJvJk~ zVC=-4nG;6kOcsNs&;%^Aisn*v2&>3J%wxR8o_fdVSZfSk52NMAs6il#Pa0x&G3LZz znI}+HH*PIy5}rLtCHRlW@PQc^$(5>&<(w4olz0LjUy-LOEiJ9?;Zi6U=y-up5W@sw z=w;5W_SR3XMwBm}WhoHIXsB57J%M>%;88act*HPkW2!K9O7K4(sEh}V7^=)a-<4io zUiVPVDP0WYhd{Y8pjR?c8^)Sm9rRbYgWe(@r^Tdz*B5kUdKgL9SYIWCBfs1q6jJct z3^lPj$YYK4O6lxC8A7OiSkeOE!~*UT>?;AbRbXXUcba7QeLhc7(ChcPz{u(z?^5G}e ztZHbwn+jPqHHtdN_^X00pFij-@mKp2<2r=0v3g$hNvzl-)WC(2JL6|*MJn+-sUSsC zj_}DFGh5RfPjJ3JFvnHx3wYc`W$waqVO-*p=R!=$)}&JCiWCP)`YJ4ig`IWMg=zel za1MTE35DbWQimMTl)C2)H*b!Ut4?q5_K7roXu4*q?hyXNwv}8 z#f7!Q4d(d4B$fy^#GUaUFF(?NvL|AOM(dR-PduCa1~WTsY5&?w>KmP}6(^sY+iTr4DTM<@0 z@I}L{i8{0kUeGnqU0&UwNd{3K24dGEh08^sXw$GkDaG$)U~m$`a#92Zf~MNUjYj)D zRjxvJ(HxiCRp~Bm(6rrX3I>lq85;|)u$0jgH4_s->Ue=Z&w^lsXKPZIK#cV@oF#E2 zOQTj?rl^DedrWDKAVnp8B5GK3m;#d;NN*=7zy`RQYQWI2)EE)$Nd2r&8rw(U#BpLG zaY(|5^Su7*ss`i6+QtEySI9KG6}D~Ca!ChM2x^4wk3GLA;4d$CWrmcnKFoX6IX+8a zDaNmccq3%e#9Eyt+fyRk{C_`7G1lbADq)bcSsbCY`LmEXa`>dX+$;iXgjT?Lf{D%<>mi3#VJ(ux&A{8UQ37 z(upBKeUQGSp#!^MZ=we7XP$v&6QLbp< z(2Zy$TNy!3wmM-OX)5rSmdY@>KK$Co48r(zhCYd)O-ED_LisLoD}^XO2LhLJmn;!Owq#MJ^0q&ZRw;5#m){`_wz>Vto~XrKDxQ#jo_ zNnI&TTU3ux3p_>DLC(5dojdjJj0|;dwHGefm5Vw-pB^#?6d(GE%7Y^}y|S|0TO=|h z5fx_BC``}D$P`RgX$pZB=an2b2jK#~3>9BgBsmA=Sib@>fK23~v(R=3RQwf{n1)dU zGTGk3fICp*8Wjlm11tfxF=-TAGt!o66m=qg%9)$tFY>!tzsI7WC03BQ)&zoTV}xo= zxxy>P8>V%rD8PY3sUU`yp=yJekhGV0OA}$@5JPaTAF1z#41Z-!-H&iK8~KPnL)HS~ z;pvPn5ox#?MlJ9rT$~f4T?tOgG$?G{%@ZqGlZA1QHGW0%p!d^CxbjE>M9X|K(o~J;$k*{ z8P!#&*|{?O<$hTmbzpM*oauF!`%8sN%0gK59MZ6)!mRN6!c<909qF#}79rbDJYFxX zCgVTC+L;pPDO2j0f(h9P z9?HT)`JgO&UCqma(>%EgT;ZQ$Ii#kw?D>oEJ$HmZZIU|4i%c2O9}ty1iH~g~Oq?f3 z$SHUvzQ)u%PjLct9Rgi8gwapryMuK+!MS*O%o9`G3)W&u;u)?crQ+F2k#SK@cP(IK5EF9$)XKqaiR9W$x}RsMj>%{BZU@Xu@utEyb1@-tYk zirm;D5cTxhvc@$euGh)hZw&v&RHr6dTCaas*fiO9glwcx?f4>P!CsNDDcVEEEO)sq zMp!^^)og>aGihenJaOfbH&CQuV)8|1wH;8@^@+d}K}Dh8Rp?*f zsuHC;t_@cDE2UM9nbQKDW@QP-fQEvAtYXD{leZQE!s*MvKM3|lN{=l)P+E<^KUgIs zs0pvF!tE>0_WC@N+<_`jKv=y1hN+#TbY>Jnn_q0xWdWb#?rQvn7b>$3F068GTyxU+ zT!qi9^IlQT;AoMHsnQ;Bv=|WyW_qhC%iT3qt};(~C5oj*<<-SpRYmMoRp}}6mcYdN zrLz~2V4_8o)w>8boaC%((kkFgdCC0EFzTJ6Q3prCWhQSq$lD;C z$Hm22V#ylASuJmAY5|?VU*fCQhGb+E#;To$)jVW9q6Y2bL`6i-by9GUs710BlEy%; znVA=oYcP-vMgV~Y(@{+c!gIHp;6)i1%oAl-m%9XIToK>bId7Bb7%xhKEulECPWc@i zgE*6pNyZAwOk?S*5B0W_QAvfoVVC9K!@gS-_NtP|fJ9DQn@%Z{eRF)M+J%IqeiEfc zO~S-i6B%i*S!AI2_&rA3)BTNtJHSXw1JVHmYBsqTxaPwn>jhX2Z2Br+3tLdaRW->Pilmz{ATqSO7 z#fWXhMlgE;Pi+49A9N$cHXbnA@dqCkfi!QR$W{Vv8xqJ%CFZDzqg7eR;zfy;2yHpd zdCF>^$dln|E8KHD-1?As;|Evw`7|F3nKvbsH6so$iYMj4ts<*f_ zv?)B8#@1tkN-!|Tf^06LL`9n@Wu&(p*029yEHaY9?JVdD^4a54NLB7UY}ykXL4oRBGINtX}>d(i+W zS1W2_g__rP$m?J8i=# zpNwkbVb={FBW&0>wUVZQqYz#=S4BgyfQDJ)G5+#mbR`sfO1wUA&{M7^^|d%)L#+0h z2n@rrqK8x@T82z+n9u?2^7B%p%(3hR)sZ4nF+0XWtnUkA-y+eNJwz7HUX5ce-XvG$ zSbFI2xRH@g5|3ON@cIMZU`;{^OGBzEyHvuw?M;KCc-sqnjpc3g94>#my7V3t;%=}0 zb#a%YZr(;gR-`e(kmL8Mm4{k%?*Ua~Bf%?X?y>CgPZd~wF@9p@@`%5H*4mJbW!K5r z3N&GOQ9Mn|*IwHAENrY@?4OuRl|tiKYFK(i;vD6FvFA|cDXxYkW$Hq4e5DDgI}$~7 zjvaXK{Y!zZ+;L~?!@;7eO}%5ZsSow7Me%m|9_*i|`K7LBvKi zvgON!u>u~o08@+jE|H)x`I#VbB3F&zN%?9(T7brvL;}ohOk6qT$U_)j?RiUn5YeGK zAiPpM?QSk)pE=DG=)8ORw zOo;V`|HrTnaJi{Mx+}zB+=vt= zw>0q_Nkt7i89BPoT_MXP35U%N*>~<&tsP`?d9+%{J4X(zk({`5u*sj;P_aH}MLkUj zkrJtaCnvV(;6&e`uPVHUX3NbS*(WpLMWQK0uwOlB@WkOpE1nQSrcKv4ayrqv$QCws zj;w@paJ?d;dey*1whzzB%@m@2r=DewtP=%EN12_g+mg&^-k;`uC${ZjAKo%NeU&HZ za-*wM(mLMS>zX)HmLl^B?#AJ;BDUSKMR?*uFRa@!helajV-WZ|kJ$$7bHE8cRa{0GbBzSr6tE}$C#Co z->gjh&2D1zlX=BkVm*c9m(j5_it1nPq~Y4kj;UqtVCO0q;;^dWjDtBhVyh}Hg&Kvv zAx|`@MQm;WVis|jFT+X~%#dG%KQVEMHhB`0&^5tdtmWKRj2*x)JmrG&7DReA;D1=T z4dY=*7|)n*%2tXnMGZM{XmP&hz=#|OiCtR_5ktQBl6c>aGk-x%WxQ{85XCb#5s~3n z*jXU!(PnEJZo`(_8lf5zbi-Gb?f1{At`w^;s8drp8)px|oyWZWqUs0p+9$k#8ch_7 zPrPc>oHZ=kX85a-vUBe@ijv3~&{tOBMk6tY0!+CkVRZ7UeZsm3v=Uoto|@e~hXRq# zsZ49nkFFfH5$*U0;)*m>3c2qIac`)As1YOzgEgWPoiiynadR}KMWo_Y2E;r zySSKluh46Pl9Zq*Vl1weyVxofRR?h7C0JflyYoH~(Ju(K&D3Rui_!iRO%RnF{eZNz z3bK+T+h;`yOOuB-V|}Qodr`!hG_Rk!0QAhT@vci8{yWT=QU4BR8fhDV*+mVaSv4?F z^@Wos{+W%K+)%0?J9P6%xuWqtHC1P9pu$Ec z1Yu=NG=3v>{4$)Dsm~gy{akemCKFkvdu$iv){oRZPKenY*H7rSDq65o<*-@2*s z0G((2+j1aLi$%CTIc=>X$TY(qEGv8i(93QBdTh&>U7XEG>eYu#a}}9NKT2v1LWB?a z$moh=E?K=}bH_&S3rx#DH+WhEV6bm6A0DJ`bsCUy4OqX*3a)SbTWByB_`F>Gb}Xv_ zXfXK~2qx4|2~A6be0HGz`G<%qg(&EP&Z#TpK6a}c@IAfMGvu5m3WU3Sjag-jOq zMVM?_F(MQRu#MtukG+4n79~hC9FA(#Zq;y`U|Zyp{&Lp!l4_sm{go%`v?GRze7aP8 zR%_ma;Pg3mN0d2}2rVfGOtI+Q$@UsLyb*&qUC-!N}_{ZP^v&RDy1;%>Ua20yy+N{zPbc%Qb@~GBc_^g zRh!iylTQq(^)i+SHKI?P@wU&GP9_{b;}jqtb92qD_5{QhTq0h(=zvf+E}*Mo7KNZj zADl=Ko{Z(ES{{#2G-$7cD`kCV<&u7d>*UcErDbT6&?#ar@S+Sj-y_JH((`0Vi*=aH7k`}R*V@LHqIRA zsD>i4!qh_a$%oFt;)Eybq1q#2%*_mzSBX5;gtoxQ0*^jbt?-EFCqPZmTq3?Y3}3C? zo+d*Lu5xjDsh$y(n6iJCNiLfFG=8*9<+Cs#1&-ua$njn7QY6*P*QDH9^LB`#2k&J` zUm&xO$TEF$2DY6=dLf1pJEU=j?1rs`cnEX3pb$uNqXxUz$f2E?p2F%rmWn__ZOFW(apGiPzax>D?%(k#gGT^aC@ z9!Y8d|I5)K8{Kft3q+T=YFN1pvN)waF$1u4pqIq{cB?9RI2@G9#*To-<&#F#kpPNyZKw{>d|D>OwSr zMHFd!O-0L~pd0G2vRH$FS+wj4c|>yo$M%AaC{D{0Y7z388I`N^Hb=NHBMDYpdD%V5 z=r{JBn3hN{SPNVpUnzHWyXQfk)O}LB@D=jbAK@`>{a?%tda-xmjGO7r{c5~l#GNWC z!t4-o8k@G{G2|Xhkmg!O$u-(G6u#uF1X$?#fXMP*UKI za+aQ2Yrk1|K^sC_wF^OtE&HI_DS8@?rcA;5cy;oW-X&PaJOn~)!I?wyX|>WT?OC|; z!t~+Fi0pTe-<2vOTMXpMP>xi?wr+f<-ZpGup~u^g1D&jyay!E|shqDOaiL7$EkSn= ztRD*N{5@B3`0Ff+^W9ala3i!iM1t_?F_i>aK%Y|zbrJvPNDwQ^^^qVQBw*Xxu+H$V>sP+UR?sCpbYC=9Y4aX5(HU4UF7R5672riA{-WiuSD0WBCKNFGeBHKPk(&k+mcV4595 zjO1&>*i&;n^IchJ_LZr5t)ckf-$V=$3S6}Zjj4Q2DK6rCek%|2s&*ZkraflH`8$@5 zxDU3(gQM^mJB9csL5->5L!c}k5qsNLXlF~p5e>(7Y|Ht8Dti~kNuB{8e~^x`{T$ z|J|@`77`DeA{G~hqCbFuTIZDZg$p7w?y4;;;logoV$&%dQ5Rd6 zD%9qp%uGm5SghiU2p{8??M>VP&axg}j$tJ3n2M`((dWt3=vzi#^C0~)Ze#6`G9*uz z@o%{V!36{9jZp;FsNOn5?&ZNa7JGucy*JMbGgE;^khnN-{1thaP>ufTo1d|eHFmH= zo4sXt_Hsng^~{~PIZ&=VY=Wb6#OO#O95J*B_hE!MrUeA?BY<9T4$L+gkjRvTO{Z3l z5~Pj@TeO8X&X5I(dQ5nlHi4S)G;H{$q2b+VC9cy)G>P(5f=n8YraIhIYlL%Bz!Hm+ zJyQzM6bMjU+2Lwx1aTtFqg@T$yz(}bT6)|h=F!LsQ#k4q^|M;fI`AyJa}jK6t2K(w z*0^{&qvIXz5ktZ-r$fk#hdTj(fEMdKYoUn9>M z-~DuqeMqy5LD>KnRbh0Bfn5F)R}kG;@$qLZ;Wl+H!br#lh+VVl;u3{87#*c;RTji1 zN-b_CqK>H!!Z?N0YnSb56g6l01M@=%Hfl}8P5z-9MnDpE%j+b5cTxm+ocC~UmdK8q zN8?0zB2(T&oM}xwEc>9^_}VZes%QyO?h+a`P8carmCc-y&Zad+gz4v`h`Tmrz{;X3k7dJ%CkzG6|-4l>dL<`mDxv1pT<=O zd3G!uT(uQbLMBCUFZV|Z(acxj4$O%!^v$sh)!nP28L6Wd?ghlD#`ps6g7;=S7G8t8 zcgQ_M@UfbJ*(cQCKwJe1!3kpa_6-dFZS~l7N8HHYkTCF;u;+aG#d7Nm0KW)`>o<4z?X1F21tlAwI_{Yk1FnCgKIw)lMST8 zn97m^YXbMnv3WfiVda~M;Zw`J#W(~j>RRy^C$DdlH53`Yao)vAHS!M)S#v%vFfVkA zZQxq$aiVY=vEv(lD74=jca5LX5L9s0YOKsT!=bfW|EjBzA@Uf+NlL?b=ZX=BzxWEPrUh4*)KhwD&5rjX<#%O0QSOz7 z_h7=YWyO%fVFj?NqQ26hQ|}IZj|vy1(A&V5b*BwEw{X~y%3-)`5AeU~E{{KUY#U~D(3H3hQ)`nfL0F^KDB@2Mr;l9iV~_v9e{S64kC(7L zKK|G1>5`BCP@F}o&GLyK5P6~|3Ho%Oh_{k*vv7bLrvu!DD4L40H`g;*AqpdmgDE}W zE|j<9$9aNtb-vi-B!rGlmQS0;k{-ZeAg^1LqvAZt7bUT`;JtFh7TR;>k*Hmxp^EKF zoX1k2`&i#YHSH7%0N`iWh4#_V_zF!$*2kN zjR-{vp0*&e=kO-HE<>iLBGIBa=MmqIvm`4&=c*G??p)z`g!P`u38Xc0+OVtP_Mtx_ zkZ5uiDRC2w_KXo51Oaz~l{Hbn4l^1{ew%o(Ze&A~;b{)9xl1Bkh_uV+v(-Cl<1n!i zn0OMFockAJ1a7p)A1F;MaFYG#dI_(SFIP(>Ej&>*s?^d&V5d%d%rd%qHtcjT5kcjX ztQFNbv z&_=i(gEyGCOOr#{uo79uxp7p*BP-x>W^~ecJi#rod=SE|tR^lRn!mu6vC$yiI&n^j2tJH)eya zi7b{wY{~JpQs7U-BXvk?6Y&Vw?P~BJ!|>@T>jXEh%ff%+uvuIL`M@%VI1#I#fGZ-Z z6FuR?bUcv{WQN>oIGQKaxP6Ep5~#EVsYz#WCrma&PUrE7=;jcKjJ(ZCxQK>i~>6ER|5V`1VN-;W-H96qr*IZfD`d zS8j@))Mjsz@2$XvxO^dwE29is({<=wz8I623sKTWK=|$}gB&HG5tl@2FgZ7-MMnLm z{2E+kC$4XaBYb@Mg>--JShJE3*XJ%uFq@2OIM&9to`#~a0DU2ers7BB203oM)gCbw zc*?6w#Uo&aCO7k4@b!a>f#d5vKYW*${if_*(TCyvI@g~@`L zvWB%m%LUmH#xB#&Ys0zr)EaHiJ9;Rm2u3+_W6cgP)|BstPRGZ%qhPqS3vw5V%?l$E z7lqIzmi{z<-8;$1+$B?C?Dquot_U58oMB-68mu3nZf~( zYzyRxYfl$%L7_K!VM*$o^1w*#1@Wn&L^E-Plxw9d_A>bji(X^g%B8+7tcc%`C-R%{ zn#R;IZd`(49(6trOKZQMtX;&65hn}cQZ(~Fn4PJ>bgumdv$G;**4{UzQ8rdY#>9wc zBsV$84A*v8Kx=hph}OJV0p+c}h6yO2d`rYnS&d?K5a-MUz3ZcT3r^hm3O1G55wC^| zb2}$y&AcjB4oO;EK5 zW|qj4Tik{duBtN;6StJ52*gEc93c$9WB^hpgN~RPcsGT0UY>mA5Q;Z(TqvaVMBc)O zt+-FjkDoXM;cj*g6$6O=(lk1?6)juh{7BE9bLV4c+CRV40}=-VHKO32oIASO8+-3; zp9as&!840qc*d4-X7oav6VKMK!LzXe3UW9aF<+}V^98-Z(4m01%i~H)0p)2#YMNUx zGdq3S#K{FSGsotQ%HTKTGDqS*{J%M8-2@%ZB8(7QE80P8YcBf*KDZ=`*8jPx#|-pe zz2elVA(dW=4NrmD|Kv`Hcmw|*C2zb5(;$6(I$U}UHiR-}Q5#3(29A{S$ai6B*H(w_ zY7xC8EH&{aaYe-N;n#jPJjgZXjtPd+f%xyijUT@zc)xDMXak@MymDT3bd1J3&Fu2S zz3OaX_>3{Wuc?kx#A@7%UR@b_`D?`J|N96jVsH-kBf~Ey0aw_#62RwA!f*VLo2Lmc zozxc7Rs?0*Yo2h?UBvyC%-V?A`-qlfE@kk%oR@{qjx*udA#fndCY!^ylMy*|&+TQvT=<+Ym>@GsyyHA1hks%L!?1)1X(V+H&Z zzc-kR4EaDX@df88`T5x{bYvD+mGNcZ;fn(z=+^}nn1f@!hE*yY)3G)^!=l!tz|Xgf z`*;PCYN7}dG3Ll9vZ>b@vb$hzM854oHfM(e_1fy)T0Qg4jnugBuv~r9gUmW3X6*$7 z!khC67kN73 z+F?1=OA=3#RFX_`Oxz49h{@lskIVc#8%S9fmvUE|)aS~Dpi@m85{uSCp`ADjClA8a zx2Q@8e4@;o(Pgn`V;H_PNka#J<0rYO%gPYpitvfQ#r=U?H(&#%^$Lc$F-h2xP?pAT z@Q@W5+`#R@Ydm1vY*VzGY!VP?r;Jm`64$HxRFV=IYl2K zjA)C;?X7`e?7hH_+_88ynkV9B;8`&iqW0wy>hX3NU$A0G{PD@i)O8JqY%rI5a2$tlg2z@n{>!?UC-o^z407UrDDlRe|34rXAzbx4qPYF zNX?Z;T-P4Dt!HHR#F6oro%rJ8&X^;730?gJkGL3Ly`o;buAC62p)02IMp-VaN+pskae8Mu@fV1 z>yX6-^;Xa5%^0sd_5+F4iQ zNuEFjze!PG&k0bRVY$p6UF3;g0lTHJqn2l-WIGve60yVP7tn-uaJ7{+)e1pE9C@=$ zRDxni_umxHqj)e5xG*cDahF{Y5_?sD)cj1^3bq|G9O6=xkbP|pZNwHIIwiysaBAOO zCx2qkH}WwPS!E1|9pM%kalSEnXBk$Z0=L!WL9aqDLgA8hSZLMZ;@ubRRIdUKOKI)l z>W&VhRX<+2S*^YBZZl_l*wWI772#imVes-&A|s=irTyoeuf47T*nVWKki`(z`3MXl z6e{7=?PvZjss|eGczH_rf?0_;4nLwq67mr-sFx*ljqz=@e65HVa>l2qSPq4$0b;xh zS^Mq@-mZ^+-$sYfy<$QTC5Od!Asa?yr|KEucbiy?hIdN?i;@zUkkrko` zjXsT~v2#HMxKw}_%tR0-Vm7`foRu)~g5%A`mYQv~YzUCie*)5I7&Q%H#ON>))SHxQ zQ@{nBqNPDK0Q0~INVGPlQ)nMA(o<0xRPzhweZ&&~Yq5;Hz>MocH~QIWA42NG)E zpEM`(!di%4m>#3Vo#Jl!8e4Hd73Ad%IdI}+jR$9dT)ygxLU}wO?o5M20dFJ$+1PNTD2t&ShVc4j zSz4PZLg^(`i0J(~>IorTvvA?~`eLzD4_n6y-)5m|6e#%{dLB;Q98;#>nzXrZEjQii({l^kqwdGNybglot->h9f z3d4l(%HWol%6wpzlGO+)hgDG`&%q?CH@AU=t!rZ(C-v~v%r1YCcsGiOMH9%MjLrPe zURmf*NLe5h`6h9hfU;rdzCzK&q^ZDJ{=(UO6&vikN(1>`aW~5Fc-?wJCKQC2z=kfu zSP5FFyOYw;NXO1wH8SGkeN(x6!8ILj1gae>*wAdrwq=3U0{Mzt(O6paw2X=i?{mPS~v>9 zR2Bpv(!cYKVf;SNuG-={sJEQCeJJk97w3%1&T@qmuuG>tU70tQ+-!LlzgX{p1|h@e z9IBz(GKk2HFs&`rNjW5RyK;lLqoW!Lz|i1h;gnDN`Sf6zYZ0-1w@!&>?(jZM5*!jSwfse6)()-hYlz z+fWWyEgIMBMJds>%^k$`s2xD3#41}$OJXE8+z8s4&rXUmM-&e^<2BxfcvLS`TEgnPWglYJj6umUipf4`v=tTXf zD}Fm2KlP`JHQo>9RY~=CiFM%~j4GHS`1wkNeTAsCVQHbae5A3ErgiF@)~R1t*Yr;P zFRrC3N)s)wjlb6aGb_h2A2Z4Rv_pDa9YM-}>h#j=0G*ZI^R9n17p@VSI)6K%bYK@;c%SR5`Q-~}B zKdBeDM%zVkLjd%Kdxhjv+;GJb6zSW=TP`$YQjw`tRm++ZLe`=(RMT(^4mv@_i5Xn1 zBV2fGa+iVJ99Je5&_pJiQA_+(F+xD~0B!UpMKc-f6xSf11l>Xwtu|)NEAG8$2+7o% zg(`NkaW`Hb z{D3JHFGQC&8pk{#ZxrUVkyiEC+k@|@MK?X#% zyXNJ^*U`=ys-cbDghQls`DRm=cX&>o!;zbf{+n|70w)o#cZ`^3l(0`~O+fl2VHUJu zojhy~UwtlW0P%-phpvWllUNEw=_m4>7)M~Wx4D|};BmLQYNH)FM>N`M*uiHY}wA5UPC0-)5aG8|M-eQ^DQ1a*9sAM7IV!tEntHs&cPhDc2}#ieOM!;iw|BK}c_zQ(JF^!P}72x=}8dLE8{26PR@ zCYd#j+zA6SL=B(mo1Z9&RNm~hTibM3#t*4FojIRLT=rHj4g3m^pdJk@I(+`rXGyt? z;##)!tJOz~#yhES$ZvwQ^B*J?(8ID`=VN@-ylZw)eiO)vL-A+JGB2W|I)zMcu{=qG zhutB(;Wuu`A->&oDlY}K>plV!wPu%kCoIVq1JlMg<=_Z15jC&Er1*==C=OCJJ*ws? z_KO3GCN>>2@dprnrPmj+UxgPkKG~m^G36$4LuR!udzGU6S@lGUChY^}O#o!WFr9+T z3riP)_wK#~3k{D7IDI9k^3E=ULkl^GS-Rs0^8I94Su;3>V9B+l|2Unbsc|^GqLaI+hpC3?MQg*^yo(UXK965nr(VM0R))hU#28rWJBU zdZ&ETZ_AOrpvmRZA=|2$t6hX8%@4!}cWrG4D-mp5+VcbT%$NAqCUtrXBEXII{}XP& zrHw~7anh*H+mFMOTK%LaOkKTd;q|0oNFjpsSXkQ7C`ahULOrM~CIVeT0dxGqC%M2) zLSCLi5M$Nc2}~3qiEsrUKOLQZj5dX+jnt#$agUiG?r{`- z$u1*?F(WaI_G@x9RnO5jg1tbBWQtOZi2X9CL`d79eJbSh<$MO0S0Ck!3~@ozG_v4&JNp>?L-> zO8ryDqrohY&{c^ttW(b$yN%zakjN6(&e@S0IP z%tRS$h9wS&O)YG$peIl?Q9vekV#rk>Y7taTuMIU`N>+7C@cz+i+WK2%8-Le~t?t)e z@Ik@US}RR46_`P`as^=j4SE$kL3s4jf&UXAsoJA~L#y;L%*uWS`jvJxm%8l& z7~E8uNZXQ@qb;bRaUNAFIlp#HK(7RM4jDJ{?7WBmB*g8Tj~>9k z_+4H76+tbGzfbri=xgBJXVU|SCbi)6JGzfL8Q%@u!e8wktdBz3iaWIrCb?(c(+&Jv zF_m{xDTQlBu9?^E{4ZwV5JlQItsc-}c7OT1TNVr0aE%>*_W3A4`(+?Rs9$vVdZOTa zC%8{t59>VlJc|xezbgrh#gW1aLoxT7x{i6%P+&{B5+5@rR_uDqKV3%mnLj@~tz6K)A8 z=Gyo6@Weyhl&viq6XL`db0T?s-`C^7K;>jLO@yLfEw87(z6J^2YBRmk1w4`yE4?#6gmOuBxcFWoFG$_K z&M?kli^~}=eXg~8a{WufZ^iXh8@7rVvsQ@7z&BS|52}-P5UwOX){UVvF27e^y>!8Z z(L}2-gqL%;{<~ZsIvk5I*d&3D>xJ#}1(Ms|G-Tu}_o>KKveGJQ76}D`DZFnl6b*N( zo>Pk2x#PJ+aDgLKR1>O$N;B=_*FH#63DR$p&JOi2(XmkWJ@LE)sfM4d(Zm~^G{W*< zkntW=?}33iw~t!;FjlkmHWF($keg$)q)JfJ`LzcFm4Go9if$`{i~GTW38#s5TL)lL zJg=j6%u|M=`jFGrt}BWPQl6II^*Sq)6evRSPhtbdnXTAX%XSg$!ppWwbfs%P(7&zK zfB7n)7XX1LpL!Iu*nU=@s-PiKx(zI{aec7A@U=Hc}J)d2+| znIaxUR%|f$@;?hJ>#fHZQq;_d6}_FTZWdzZx=ANVi_Wmf9i1(Tyt9SPF#~zIv6n;O zM}_l?>GT0i4y`^}?4+8(_PrYy2X<#jHT?jvSO6eKK$a0h^3gb>yc~}J^<#MoY+xdz zngIR5U`u=0i$PhRphm9>Nq`|qeaz`q4z>9is#W6*Ld}u?yWy*Mg1KBnt4iGAwR|0h z0>$1%qCrTpU-W@ajVDv0h6p9tejB-Tz#?bb^X&mkoVJLdFoRhsF23?vmWME>$U+M| z#jSR0H=wZ|Fsz8z)de)LN7hS7W1v`4qp13Xe0gDo@7P!xlMg+O+~OU~@@+f%(nLA2 z2ash%`KmGf9Z_$3?*YP}lGM)@Nxe+vr}Cl;oo7NKU#=i>ri#&&sOq|rCiSyQ>YBfU z%Ji=9h8G;wpv_f)#$|aQTCIkzyDI8G69Ut9T@CsLRkYebde63GSGK^Ww(CX~sVa*?)8g)`#^`y3gzQ@gYb&bhq<%Q+7T)%j zN=EiQgBnAw*k|=R^#2iERC_~tp%#NSeQKE2#h2k#q0z(Q4lzwj(MNYPBPP7r{VTMe zkdn9CVpg)gG6jgdY(Uk=XXs$ei}Sc8{}n!!7ENcrg0Ph4ZSIX`rP5CG#HArry_+i(u*&EYv_orMH46eVz~lk9lxRC#~TEU&&TuyQ`VjLL9H# zbyt-$1dD5i0o2E#O6WFLA|#As&}9?i(UxtqIF7x(^h9%UrG@ub;D3fMYU_xJA-5h1 z5O_rL?OR|`L>2@*XTBNmmfMZ*P}tVtrv{UwdksGpCqUHDAv;g?$xO7V3PckaS|BE!0^x z!!f?o~Y)hJ~4a$ipqY$_fbn zXR?jG`i6FD&5?Zu1}B}NLNRuu*JUp_ud7o=s0@OP;7lCsy*e~x78 z(MJ#G?*|Fp>FTDci}ETh}>}mApi?S<@!~D z5|(1&-N&qAf1eivX|$#(I2fgm=Q;;6Dqo13A+i`!(sV)U)R1pJ1&(S6-yf0XeIO~Q zQM?UPB4ZwLY`s5ydUug9|4c7!jy-y~FgG7uQ$NH*o6;ixds1lGyCC%6^UhQ1X9lu0 zE1AgElD}9rC$EWmLZQT`fa5GwerSO6-&!Hf-KZcO3TwtNnU&62DFRi0G=cx%@xUD~ z_Du8?hPWaUWzOoI=0*4H5YcKlBD+m_XoXnjzU#Ino|h7OdaEn`ZI5O}s!Pi{JSgA! zj2T;TVj*C`%|=JUmT(54gB4W4$tWm!GXWT zybPSJVwDyAw2H*VS^RS!zdwi3_83XYdKpq{VwD{4Rw5kUPsVyhzrb%O3bm}o@9-OV z<^Zqu=(V>9FH%*rN?l~++DbiKP@zmhF$aUR4OH=brMkc^a3Ol6y} zT(yUhx4=E^26{y`?WXk#qN&)EIjE{{UWv6EHfv95CRn(NZtZQZl1T5jRIM2}> zu-K2L1i+O_(MpdPwK^>m0Ag?l7^vFv7AlI?w4zHIElrN!sk4(#K$oBHmJFIC=<}m${O<_Ys@1l2Y# z|AWK~-7_AYeb;DpCCBf8>+?8mWm+px4nWA!=WZnsdS8N!+N%Rrd8xN;7j$HaK7c;< zXct;~1yiJR;tII6_TD_w^kf_7_XhaTvn+n@x)52G0t5T15UsAg!%teg`R zsQR7my7sP8o-Qo$QKf|BrOu0+DxexQ<)s3_)kVrq)nfnA_#yN6w0eOxC)k!HSDloM zMd6`sF0}OpwO$RQ(D+u}!WY4&H=DV`I+7BX|4sc>?{JNEmd$z9P1HfCiX&kxN8Y5I zLEwh_{86`#bXQ#Qd~lfUpfNQRUj+HqaVfq)ngnWXw>Rm}Z5qp;Zt9wO<-xKCaQK=o z<4R+JoWEUpHarQ2@jScd(CXxS@PN~F-;pIfH3TcBIjj0gD;$(tJCN}-brns~m8)g^ z4nX10%y+pItJoy8FzY+ADZ(&!$9ZaeRgb!dK`sF`@QjQqsa`hIustHvhK zCIOU___QH+8AB*6#EGH7o7H3U=#A5EWz+NX@@O?a%W1skA;mB;kNz$-@gyAVz!;kE z;#H{hl#5R?2rFR0eSTgNDy!K`_$dSZJKs~> zv4L^SQj?%hK0Ed3BAY+66n`52F?k`!TIvvp4&}p3C?DQvin0=ioaK_$=waaO_r`3Y z7@QVc*zaLt2@Mb$yLD6v$3}K{D>iZxmKjX~QKhh@qVN6K!*-+I+?P66=`EcS(DY)_ zJO(mdEQVwx#AZ#gjo5G6D-AiNEkz%b+3Y zVGzgEdDznG(k3AjSB%;bQ)Z?du8zxW$hhscH>$RI0?Rj2p%E9RMyd6+oJz)IbgxpcyVL+V5{#n63qqjDdfxxTN2O`wX^2SP?FKE%YtT%&aMc{$W_^cClR=#(v z-W1Cv(-I{F54he-!q4Z%9NMk87zmv=2M_I42%0=}6gZ?>xKG_mV{Kj} zJu#BUEL$Il@54wP2>hv)58n`JC)Svyy)u0qJ@G()Gx4EWmIb8@+=t zoG`ijeq{D~cIP>`Au@`az2Uw*G=5aG>8Tv>!yq0Hgz*VO{gu*uM zLaRdQa7H@7MlfjA7I-d<;udbYFAvS$Ol;w1VhcBuTj0#Mf|+kcX682^2H$*`_{NLR zL$e2RaCzhb+qF9I}R58{!Rp* zlYpmKt2rB(bt8|>+OpKyY~RCuL)2inv_UOE8(-byB^Vw_A-1#l!`_ z3HWg?&X4#e&X03d{5Ti#<6N8{=R$s*i}T~$K){c4aekZ|2>5X>#*YWI6AScv3rbDG z$^>TELgJcOuNefo-eP%Mc2X@+^o6)KKQH&0F=}679a@}zoxJ@Sth2Bn>ljP)71qHm z9IV5iyp0vCgBQBIpN+xz3hOYE>G#8*qU-GL$~wlLE39*)kMr4s2F5ni)G_$q(N*>~ zWR<-QQO^sj^g{*I`2BA9Q+%C$T}RD;!aDSXP+?_2TeKmltIU+qU<-W@U3C*W>HYdb ztIGn5%kb9GJr|MSr=V@9Zch)Qw%TLUXD=d--D!ST-$9yY7L^sL3B3FWCWr}=)CB(8 z4kn1ni}(cdfk^ce%*Uj3Y67nw1{1_YcxnQ#6b2K-1b1ozFU*4pVp2UdLF^dkV+2qr zC#`N4JCt6N`NJtp*8c%51T}~K))p2coIV}h+I`tB&AhUo%R_}Ec=O)6Ad|G!D_sMj z_|WN?{6NQ7_;!B6VeS4tQetW`>n)D9F9G0FNVqJ}f;@m<)}D5=7-#jG%yU>_ve<#L zMo5`DvOfd zHf64uJgf%G3$;90G3-~*A5deQ;_`+kcsLlrKc@eT1dObg zCl26aSl8g4fZ{!Xk5{v(ZB;!}j1M_ej1Q}4im?u7>YsJ&?qUSbnPLQgCMsogCVz~_ z9m+9J>=ff$`<1AjS)ea^bpHzXVho@g0O&cc2AgQk>x1UfhbZPaBSt07B^)7sriqz1 zGEU!-j3}gpWz!#CnYjs#`P$VhH{l74Dd$%z*5cfDrQ#|-%B`Y{B$l|1z#X$Om{_`l zpaaAFnubRQv(^%uM)Pdlj4o*WnSmAX z-t@H_S3desHhwL!-n0ZtsaD8=Jh_+G!wU{Lz(}4~f#$PR0h9enj~Z zx^{-XxWAf3-8>?8>|PDO_d-R# zkMVB!y}v8<``8^Be(!HY{XTXRhTr>pPrr}dcj5Q`cGK@;w_5nU7k>JETjqSS+F%p7bAM#d1X8nDS;SC~#JW zGvig$d-7m=|1Q8rIZ_99+{a4{SUFRMWXw#F0!4R1P&9?77)`)V1cXFH;gs8n4lhR* zEC8wK!U#O>@?^kaeeeOQp+%6+3;{`{I@D zt_Cal^+O=4;l3W6`c=I{gvLIfg!o}XAoU9SmapxG9zh*yEtDJ^%kuDa@$v5D^MPD) z#q1u+U8GiJD3F#sQCg3A}_N#X# zZ*ch{$z_qhYD)?Qt-Y|!33GMyZ$Cl4Nnj4Mp0jR*C8u^tCodynGL z67ftL`W;Fj^Vt#746q3%FY`SE^PHGK$IPD39G!lE+z(qUzWyB5;yYJH7g~HTgt8dT zec}_HLfTRyGHdLAMxe$Xq5MU2e*rZ`Tie{=YyYjuFHxG-vJTA9s=*|*(lJc8*6k}w z_;@h>=!#Usd_?Y&aI@;oC3)6Gn@3+;GYeJuL%*;*#NXL$I)1CqXowGeS3*I^pESYS ze|3KuN?Y>DDv* zhrnOHs@s)O_p+|l0=jC**WW@ZDd=rlDUZEo6Z<+T>Itu0Et*3bx;=2)apSS6hkNWK z*vW3+j$2*N4nM=E(t@MJs{<$GeZUkbg=x9uO0V&tZ#OwRzVtX;5cDiv15;lM zoGruG;AnV$P24KwX~4<_<<}UOM#Mr8Z0S^w42G5rT6U~sGpo*=|0Gj#P6tD#qtu*%xS77BtJUcT*?;f zTXGGoFkSKc0VG(~01|-awqFE90H9H|E}6K*hrP^9*k*SbVj^F4y$h|8ZSIzZjK4#dC|tC8C4E`XhHcGEa1vgdZv69H3mNM z{bR)C&q3f+dG+G8LQ~0^d-m5huSOocRi29vkWbmlV}f<`0>aIOTDrDpYw-*%X9Z-5 zsS6w<0OUC=%8BmW&*WI?QkG-1I6*%!EZ#pLmf3BluoVjeS`rj+Iyp-rii2_*p5i=l zF-px;exH{*y66hV#wUrIW#vobOpb@Nj9ZzHjy~MntYD+p)Ne;ZVKR;-AO3tUH$TaU zx%H9cSAr9T$~5mf=uZ<)e~|qV&mcDgG`NN|u3Q@X!p6 zryP&(ciT-oTCB4J%08-m<3Ef()7sif=tL7n*p;=BBNfWXOmUR#)(S(_`f|cB=m++O z%!}JC6PaZ?f|aSzzNB=J^;!de$D`UsrDj+>%P63cUWDd*L(=ZP`BII7`Jw+aia~J({Y6trkSL6(DdBx`cN6hZ| zLy9b3yo++(7c=Q^9Pk0!putR zOZ!Di3UL-If%xA^-NxA4v>hp3@;0f}lDHZzbgowP8vGnC5Eywi{KJ}tq7A{w%QjV2 zg6B(Ar7P&(m~$m;HJ%emyKy%H4x0xXs<5ikS^_T;e&wy`@46hJ9Y~>#60~4BfA3dv zY!Z+hKg93LTLm#ka;Xq<08PY?TXJ062SDbCZ5m%>NUZG(ZqVTi*DmnOH`ULx%qkc4 zXWrcd=Aa{=`l%++^76Eba}k2$cb@a@8MO;QQ8_L1$4RrSy7YVbFGtS~w$ZyRa3YR~ z%DK+M$NsHwz73V-ZfG~($X!%>+=Go(IotWfY@E}*m~(R;<($v>Vy<%1{+{?I58<>h zi&kbyVGFN?`#}~FZ(k>>d(0Gp3w)DDAJoxJy(gx6Kh)LduuvzudzI+vcP0p%9y9g% z)-#D|eZom8NP1+|f}lD~fTH8VDX0h28%_NvJxO@3fEu;%#eW8c^A@qk#b=-_q9yjG zn2urK4Di9ZlNo-lME(?(pi4{@es!(#qUwV09a9D-?Hkeyb*r|)LKP$uaakmHuo#(M zbv8aP=5o3-E}1Gl=WaU3cc%pix~nO#sDrX-TyJQI&*EmS22CwoZ%Mapx88)T*TI%y z`S#?D$$_sSFx3y~PRz|U{B;Y@WAy=M5^bk)Q|RR^tna27k!j2m_~bk{qHYqcS00*4 z7EF@6cVE0cyZIdhm7AH`4S9#iNqcFq7WICk-U;pv#%A+|m>#f-=KX>H1jfoe;QgYt zZWa-Gs+xN_x+i;A>Iw%^pd3yomqE|8f4Y19ZD>iq_dWY?-W_zF1tqZP~fgknkVLeXj-fw3s7b;~MD z{rxLoiY7F59XcUsT2s9)VlCtrexX6FS68y{CUDVd?LzgoJorbWgRf%s{s5ahq93tL z9;LgW#7v)+R!RpFOfh2B54ZXtS+3X0f7|yts`G`HB8{kCCm9b$rpzL5Cf=+%FLz8P zcw{XhF%qkg!!#4Ar2sLZ&LAJI8JQWOlmg)d8(0tGB(qY2;%HZ<)7* zt7uFQ7bev*Xc5)h?~~Ee0H1}y@Dj#6cTLx!mGk@!!bI*cj(*;*y+DZ}ZK|D~FRfO; zvDktd*umy9miz3vwV`5$*Tb!?YGjP(H3j3(LscK6dsm;E4^UyxZkJ-3 zb)+#}^#)HD+!AyEFouS>qnqrgS{qQotLJ(@GM6HUxg8X{uDrCH`Hr~rIWEIU{{LI@|M_@A14kbRPUQb?RocM3P=Q6n;ClBu$F19b0u3bIX7^yc3k@y_P%+BgqrT|I_s|G2G+7CFZQ0)n^&?s zSGGUNu3x%&`NK;eqoyRT7WGGT@XPGxrR>(F>_ds`d@Q<0nph7F{G(&ILz>N;{&>u! z>F(w(=Ao$vNQMzPX2L6h+IJ`j$sEoHVS=yvy!Oh|^9i&bF(YI=ZdC(wqL%SWvTXvy zY3%AQNFkpsL^U$FByu}GZZ3+cEri1pvodFW3M5sq4jr0X%0EY)B2DxbfT!y^ZEmt*}9Fl-K{?^A(_*cqH z5~7BPc{_ML@bn$&W1VYPiyF1WRvCc1g%nqwTVbrHh*lGkTC*`Xewy zv9G3Txh!%AElX#}2B5N?NuluDoN=Bkm#W$*L z)KPfPs1V`)e~P?(DTZm9K2M{U(^Kgoi|p9<;#lb8UC8T{SBN=xLbobo#-tNA2_*e7 z>zXig4^*~P>*do{R@GH%w3&UC*u@>$q<26vjD-gK4O z%(jqJz$|Ju1}x!q#_W#=M7Q#`kRfdVzf9rC-4YXxCNdcCYzt~neY>TPK60nV*|kEv z22SZ3)CbrHm|au}EL4L169c=>%FqglkoOPCR&S(URPd(VLu&(NPc$Q=w9vmd3sp5J z;w_{^xDMnoADr*NC~3nBgUY@d8hn3egWl9dsPgljh1Fwt05lZ*SC*I9aE6UbA3=|r zL&KWSVQ9?2)1n0CIY+uu8CIDrbznwyWJ5E%+OYth@>+;iqsNVsf8ZdcKh%V%KNlhc3( zQ*7iy4AyI)rz%`SQkZqKjulG>_bJBM+gvGHFE?FJ_tOD|+O_m*piGh7yS3hEwv6a) zZEVn~S7xO5u^|crRR{9qZBa&w2%qSTchPVJ78!fR)|=%yZ{wzTA5PO*Jh%E1Hv=g^ zE5B--)>4a&cB(0DY!e6kkEh>XnqB$wy=)g%)w3sj7=`pA)XweN=bxC%xL^Qtg?)p-x6x_L+tDreB7agmpB3iLWe*t*LJ5Un5zZN6mEJ{=6X& z7k6;Kzx;6Z{x4z0(k;z{hFgRXcO+GaFF_np^WamP_&cFFE)`x({5cP(w&)@&@^8GZ zt#_fws{A`qUVe;}&!-{91#7yE-PDcoX$NE{Z#}+

^KfV|okKizZq(@OcaJplbR} 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

!I@-Zjvbxh;nJ}Q$iFdu;? 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 literal 0 HcmV?d00001 diff --git a/images/appicon.rc b/images/appicon.rc new file mode 100644 index 00000000..bbc88e2d --- /dev/null +++ b/images/appicon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "appicon.ico" \ No newline at end of file From 5d0529807d1b55974da896bbd99276d5f929c33e Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 15 Feb 2024 18:18:21 +0100 Subject: [PATCH 016/101] Base: replace C++17 deprecated codecvt_utf8_utf16 --- src/base/string_conv.cpp | 41 ++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/base/string_conv.cpp b/src/base/string_conv.cpp index eb9db8e9..d3e241b6 100644 --- a/src/base/string_conv.cpp +++ b/src/base/string_conv.cpp @@ -19,7 +19,6 @@ #include "math_utils.h" #include #include -#include #include #include @@ -42,14 +41,29 @@ UINT getAnsiCodePageForLocale(LCID lcid) return acp; } +bool toUtf16String(std::string_view str, UINT localeAcp, std::vector& utf16) +{ + // Compute length of utf16 string for memory allocation + const int lenStr = CppUtils::safeStaticCast(str.size()); + const int lenUtf16 = MultiByteToWideChar(localeAcp, MB_ERR_INVALID_CHARS, str.data(), lenStr, nullptr, 0); + if (lenUtf16 == 0) + return {}; + + // Encode to utf16 string + utf16.resize(lenUtf16 + 1); + const int convCount = MultiByteToWideChar(localeAcp, MB_ERR_INVALID_CHARS, str.data(), lenStr, utf16.data(), lenUtf16); + if (convCount == 0) + utf16.resize(1); + + utf16.back() = L'\0'; + return convCount != 0; +} + LCID getLocaleIdFromName(const char* localeName) { - auto fnToUtf16 = [](const char* strUtf8) -> std::wstring { - std::wstring_convert> conv; - return conv.from_bytes(strUtf8); - }; - const std::wstring wlocaleName = fnToUtf16(localeName); - return LocaleNameToLCID(wlocaleName.c_str(), 0/*LOCALE_ALLOW_NEUTRAL_NAMES*/); + std::vector wchars; + toUtf16String(localeName, CP_UTF8, wchars); + return LocaleNameToLCID(wchars.data(), 0/*LOCALE_ALLOW_NEUTRAL_NAMES*/); } #endif @@ -69,22 +83,13 @@ std::string toUtf8String(std::string_view str, const std::locale& locale) if (localeAcp == CP_UTF8) return std::string{str}; // Target locale is already utf8 - // Compute length of intermediate utf16 string for memory allocation - const int lenStr = CppUtils::safeStaticCast(str.size()); - const int lenUtf16 = MultiByteToWideChar(localeAcp, MB_ERR_INVALID_CHARS, str.data(), lenStr, nullptr, 0); - if (lenUtf16 == 0) - return {}; - // Encode to intermediate utf16 string thread_local std::vector utf16; - utf16.resize(lenUtf16 + 1); - const int convCount = MultiByteToWideChar(localeAcp, MB_ERR_INVALID_CHARS, str.data(), lenStr, utf16.data(), lenUtf16); - if (convCount == 0) + if (!toUtf16String(str, localeAcp, utf16)) return {}; - utf16.back() = L'\0'; - // Encode intermediate utf16 string to utf8 + const int lenUtf16 = utf16.size() - 1; const int lenUtf8 = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), lenUtf16, nullptr, 0, nullptr, nullptr); thread_local std::vector utf8; utf8.resize(lenUtf8 + 1); From 7204600cb328a141b806bf22c9573c59e598022f Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 19 Feb 2024 16:50:01 +0100 Subject: [PATCH 017/101] App: fix compilation error with Qt6 --- src/app/commands_file.cpp | 2 +- src/app/commands_file.h | 2 +- src/app/widget_home_files.cpp | 2 +- src/app/widget_home_files.h | 4 ++-- src/app/widget_main_home.cpp | 13 +++++++------ 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/app/commands_file.cpp b/src/app/commands_file.cpp index e8a77db9..a4951455 100644 --- a/src/app/commands_file.cpp +++ b/src/app/commands_file.cpp @@ -211,7 +211,7 @@ void FileCommandTools::openDocumentsFromList(IAppContext* context, Span(&fp, 1)); } diff --git a/src/app/commands_file.h b/src/app/commands_file.h index d984e978..65db0ec7 100644 --- a/src/app/commands_file.h +++ b/src/app/commands_file.h @@ -16,7 +16,7 @@ class FileCommandTools { public: static void closeDocument(IAppContext* context, Document::Identifier docId); static void openDocumentsFromList(IAppContext* context, Span listFilePath); - static void openDocument(IAppContext* context, FilePath fp); + static void openDocument(IAppContext* context, const FilePath& fp); }; class CommandNewDocument : public Command { diff --git a/src/app/widget_home_files.cpp b/src/app/widget_home_files.cpp index f6be0ef2..52b8e4a6 100644 --- a/src/app/widget_home_files.cpp +++ b/src/app/widget_home_files.cpp @@ -193,7 +193,7 @@ class HomeFilesDelegate : public ListHelper::ItemDelegate { else if (fileItem->type == HomeFileItem::Type::Open) emit m_widget->openDocumentsRequested(); else if (fileItem->type == HomeFileItem::Type::RecentFile) - emit m_widget->recentFileOpenRequested(fileItem->filepath); + emit m_widget->recentFileOpenRequested(filepathTo(fileItem->filepath)); } private: diff --git a/src/app/widget_home_files.h b/src/app/widget_home_files.h index 93cdb222..25edaa0f 100644 --- a/src/app/widget_home_files.h +++ b/src/app/widget_home_files.h @@ -6,9 +6,9 @@ #pragma once -#include "../base/filepath.h" #include "grid_helper.h" #include "list_helper.h" +#include namespace Mayo { @@ -20,7 +20,7 @@ class WidgetHomeFiles : public QWidget { signals: void newDocumentRequested(); void openDocumentsRequested(); - void recentFileOpenRequested(const Mayo::FilePath& fp); + void recentFileOpenRequested(const QFileInfo& fp); protected: void resizeEvent(QResizeEvent* event) override; diff --git a/src/app/widget_main_home.cpp b/src/app/widget_main_home.cpp index ce67353e..5b6a45eb 100644 --- a/src/app/widget_main_home.cpp +++ b/src/app/widget_main_home.cpp @@ -8,6 +8,7 @@ #include "ui_widget_main_home.h" #include "commands_file.h" +#include "filepath_conv.h" namespace Mayo { @@ -29,16 +30,16 @@ void WidgetMainHome::initialize(const CommandContainer* cmdContainer) IAppContext* appContext = cmdContainer->appContext(); QObject::connect( - m_ui->widget_HomeFiles, &WidgetHomeFiles::newDocumentRequested, - cmdContainer->findCommand(CommandNewDocument::Name), &Command::execute + m_ui->widget_HomeFiles, &WidgetHomeFiles::newDocumentRequested, + cmdContainer->findCommand(CommandNewDocument::Name), &Command::execute ); QObject::connect( - m_ui->widget_HomeFiles, &WidgetHomeFiles::openDocumentsRequested, - cmdContainer->findCommand(CommandOpenDocuments::Name), &Command::execute + m_ui->widget_HomeFiles, &WidgetHomeFiles::openDocumentsRequested, + cmdContainer->findCommand(CommandOpenDocuments::Name), &Command::execute ); QObject::connect( - m_ui->widget_HomeFiles, &WidgetHomeFiles::recentFileOpenRequested, - this, [=](const FilePath& fp) { FileCommandTools::openDocument(appContext, fp); } + m_ui->widget_HomeFiles, &WidgetHomeFiles::recentFileOpenRequested, + this, [=](const QFileInfo& fp) { FileCommandTools::openDocument(appContext, filepathFrom(fp)); } ); } From c19274156cd7f38bfea230b6ce92d893350d3bd8 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Wed, 21 Feb 2024 18:26:54 +0100 Subject: [PATCH 018/101] Base: add 'toUtf8' option for to_stdString() Relates to GitHub #259 --- src/base/string_conv.cpp | 14 +++++-- src/base/string_conv.h | 2 + tests/test_base.cpp | 85 ++++++++++++++++++++++------------------ 3 files changed, 59 insertions(+), 42 deletions(-) diff --git a/src/base/string_conv.cpp b/src/base/string_conv.cpp index d3e241b6..8c8dbf07 100644 --- a/src/base/string_conv.cpp +++ b/src/base/string_conv.cpp @@ -67,7 +67,7 @@ LCID getLocaleIdFromName(const char* localeName) } #endif -// Convert string 'str' encoded with locale 'strLocale' to UTF8 +// Convert to UTF8 the string 'str' encoded with 'locale' std::string toUtf8String(std::string_view str, const std::locale& locale) { if (str.empty()) @@ -135,7 +135,7 @@ std::string toUtf8String(std::string_view str, const std::locale& locale) // Couldn't find locale charset... if (charset.empty()) return std::string{str}; - + // If locale charset is already utf8 encoded then directly return input string if (fnStringIEqual_c(charset, "UTF-8") || fnStringIEqual_c(charset, "UTF8")) return std::string{str}; @@ -190,7 +190,7 @@ std::string to_stdString(double value, const DoubleToStringOptions& opts) std::ostringstream sstr; sstr.imbue(opts.locale); sstr << std::setprecision(opts.decimalCount) << std::fixed << value; - std::string str = toUtf8String(sstr.str(), opts.locale); + std::string str = sstr.str(); if (opts.removeTrailingZeroes) { const char chDecPnt = std::use_facet>(opts.locale).decimal_point(); if (str.find(chDecPnt) != std::string::npos) { // Remove useless trailing zeroes @@ -202,7 +202,7 @@ std::string to_stdString(double value, const DoubleToStringOptions& opts) } } - return str; + return opts.toUtf8 ? toUtf8String(str, opts.locale) : str; } DoubleToStringOperation::DoubleToStringOperation(double value) @@ -234,6 +234,12 @@ DoubleToStringOperation& DoubleToStringOperation::roundToZero(bool on) return *this; } +DoubleToStringOperation& DoubleToStringOperation::toUtf8(bool on) +{ + m_opts.toUtf8 = on; + return *this; +} + DoubleToStringOperation::operator std::string() { return to_stdString(m_value, m_opts); diff --git a/src/base/string_conv.h b/src/base/string_conv.h index 4570ee52..6cbd7e0d 100644 --- a/src/base/string_conv.h +++ b/src/base/string_conv.h @@ -69,6 +69,7 @@ struct DoubleToStringOptions { int decimalCount = 6; bool removeTrailingZeroes = true; bool roundToZero = true; + bool toUtf8 = true; // double zeroPrecision = 0.000000000001; }; @@ -79,6 +80,7 @@ class DoubleToStringOperation { DoubleToStringOperation& decimalCount(int c); DoubleToStringOperation& removeTrailingZeroes(bool on); DoubleToStringOperation& roundToZero(bool on); + DoubleToStringOperation& toUtf8(bool on); operator std::string(); std::string get() const; diff --git a/tests/test_base.cpp b/tests/test_base.cpp index b28040e5..2c1d0de7 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -54,6 +54,7 @@ #include #include +#include #include #include #include @@ -67,19 +68,34 @@ #include #include +// Needed for Q_FECTH() Q_DECLARE_METATYPE(Mayo::UnitSystem::TranslateResult) -// For Application_test() Q_DECLARE_METATYPE(Mayo::IO::Format) -// For MeshUtils_orientation_test() Q_DECLARE_METATYPE(std::vector) Q_DECLARE_METATYPE(Mayo::MeshUtils::Orientation) -// For PropertyValueConversion_test() Q_DECLARE_METATYPE(std::string) Q_DECLARE_METATYPE(Mayo::PropertyValueConversion::Variant) namespace Mayo { -static std::optional findFrLocale() +// For the sake of QCOMPARE() +bool operator==(const UnitSystem::TranslateResult& lhs, const UnitSystem::TranslateResult& rhs) +{ + return std::abs(lhs.value - rhs.value) < 1e-6 + && std::strcmp(lhs.strUnit, rhs.strUnit) == 0 + && std::abs(lhs.factor - rhs.factor) < 1e-6 + ; +} + +namespace { + +struct frlike_numpunct : public std::numpunct { + char do_thousands_sep() const override { return ' '; } + char do_decimal_point() const override { return ','; } + std::string do_grouping() const override { return "\3"; } +}; + +std::locale getFrLocale() { auto fnGetLocale = [](const char* name) -> std::optional { try { @@ -105,15 +121,17 @@ static std::optional findFrLocale() frLocale = fnGetLocale(localeName); } - return frLocale; -} + if (!frLocale) { + frLocale = std::locale(std::cout.getloc(), new frlike_numpunct); + } + else { + const auto& facet = std::use_facet>(frLocale.value()); + if (facet.decimal_point() != ',' || !std::isspace(facet.thousands_sep(), frLocale.value())) + frLocale = std::locale(frLocale.value(), new frlike_numpunct); + } -// For the sake of QCOMPARE() -static bool operator==(const UnitSystem::TranslateResult& lhs, const UnitSystem::TranslateResult& rhs) -{ - return std::abs(lhs.value - rhs.value) < 1e-6 - && std::strcmp(lhs.strUnit, rhs.strUnit) == 0 - && std::abs(lhs.factor - rhs.factor) < 1e-6; + assert(frLocale.has_value()); + return frLocale.value(); } // Equivalent of QSignalSpy for KDBindings signals @@ -161,6 +179,8 @@ struct SignalEmitSpy { SignalConnectionHandle sigConnection; }; +} // namespace + void TestBase::Application_test() { auto app = Application::instance(); @@ -599,27 +619,19 @@ void TestBase::IO_bugGitHub166_test_data() void TestBase::DoubleToString_test() { - std::optional frLocale = findFrLocale(); - if (frLocale) { - qInfo() << "frLocale:" << QString::fromStdString(frLocale->name()); - // 1258. - { - //QCOMPARE(QString::fromStdString(to_stdString(1258.).locale(locale)), QLocale("fr_FR").toString(1258.)); - // Note: on Windows the QLocale unicode thousand separator is different from what's returned - // by internal toUtf8String() - // to_stdString(): U+00A0(NO-BREAK SPACE) - // QLocale::toString(): U+202F(NARROW NO-BREAK SPACE) - // Caused by usage of ICU in Qt? - const QString str = QString::fromStdString(to_stdString(1258.).locale(frLocale.value())); - QCOMPARE(str.at(0), '1'); - QVERIFY(str.at(1).isSpace()); - QCOMPARE(str.right(3), "258"); - } + const std::locale frLocale = getFrLocale(); + qInfo() << "frLocale:" << frLocale.name().c_str(); + // 1258. + { + const std::string str = to_stdString(1258.).locale(frLocale).toUtf8(false); + QCOMPARE(str.at(0), '1'); + QCOMPARE(str.at(1), std::use_facet>(frLocale).thousands_sep()); + QCOMPARE(str.substr(2, 3), "258"); + } - // 57.89 - { - QCOMPARE(to_stdString(57.89).locale(frLocale.value()).get(), "57,89"); - } + // 57.89 + { + QCOMPARE(to_stdString(57.89).locale(frLocale).get(), "57,89"); } // Tests with "C" locale @@ -633,12 +645,9 @@ void TestBase::DoubleToString_test() void TestBase::StringConv_test() { - std::optional frLocale = findFrLocale(); - if (frLocale) { - const std::string stdStr = to_stdString(14758.5).locale(frLocale.value()); - const auto occExtStr = to_OccExtString(stdStr); - QCOMPARE(stdStr, to_stdString(occExtStr)); - } + const std::string stdStr = to_stdString(14758.5).locale(getFrLocale()); + const auto occExtStr = to_OccExtString(stdStr); + QCOMPARE(stdStr, to_stdString(occExtStr)); } void TestBase::BRepUtils_test() From e5e78c40405e64f7dc42dbf5dfd27687479757b9 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 22 Feb 2024 17:43:16 +0100 Subject: [PATCH 019/101] Move QMake related build files into scripts/qmake Relates to GitHub #53 --- mayo.pro => scripts/qmake/mayo.pro | 96 ++++++++++--------- .../qmake/opencascade.pri | 0 {src/app => scripts/qmake}/version.h.in | 0 version.pri => scripts/qmake/version.pri | 6 +- 4 files changed, 52 insertions(+), 50 deletions(-) rename mayo.pro => scripts/qmake/mayo.pro (75%) rename opencascade.pri => scripts/qmake/opencascade.pri (100%) rename {src/app => scripts/qmake}/version.h.in (100%) rename version.pri => scripts/qmake/version.pri (80%) diff --git a/mayo.pro b/scripts/qmake/mayo.pro similarity index 75% rename from mayo.pro rename to scripts/qmake/mayo.pro index 62b50120..3379fada 100644 --- a/mayo.pro +++ b/scripts/qmake/mayo.pro @@ -13,6 +13,8 @@ CONFIG(debug, debug|release) { message(Mayo version $$MAYO_VERSION release) } +MAYO_DIR = $$PWD/../.. + # Check Qt version message(Qt version $$QT_VERSION) !versionAtLeast(QT_VERSION, 5.14) { @@ -86,7 +88,7 @@ macx { QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.15 LIBS += -liconv # QMAKE_CXXFLAGS += -mmacosx-version-min=10.15 - ICON = images/appicon.icns + ICON = $$MAYO_DIR/images/appicon.icns } win32 { @@ -94,54 +96,54 @@ win32 { } INCLUDEPATH += \ - src/app \ - src/3rdparty + $$MAYO_DIR/src/app \ + $$MAYO_DIR/src/3rdparty HEADERS += \ - $$files(src/base/*.h) \ - $$files(src/io_dxf/*.h) \ - $$files(src/io_image/*.h) \ - $$files(src/io_occ/*.h) \ - $$files(src/io_off/*.h) \ - $$files(src/io_ply/*.h) \ - $$files(src/graphics/*.h) \ - $$files(src/gui/*.h) \ - $$files(src/measure/*.h) \ - $$files(src/app/*.h) \ + $$files($$MAYO_DIR/src/base/*.h) \ + $$files($$MAYO_DIR/src/io_dxf/*.h) \ + $$files($$MAYO_DIR/src/io_image/*.h) \ + $$files($$MAYO_DIR/src/io_occ/*.h) \ + $$files($$MAYO_DIR/src/io_off/*.h) \ + $$files($$MAYO_DIR/src/io_ply/*.h) \ + $$files($$MAYO_DIR/src/graphics/*.h) \ + $$files($$MAYO_DIR/src/gui/*.h) \ + $$files($$MAYO_DIR/src/measure/*.h) \ + $$files($$MAYO_DIR/src/app/*.h) \ SOURCES += \ - $$files(src/base/*.cpp) \ - $$files(src/io_dxf/*.cpp) \ - $$files(src/io_image/*.cpp) \ - $$files(src/io_occ/*.cpp) \ - $$files(src/io_off/*.cpp) \ - $$files(src/io_ply/*.cpp) \ - $$files(src/graphics/*.cpp) \ - $$files(src/gui/*.cpp) \ - $$files(src/measure/*.cpp) \ - $$files(src/app/*.cpp) \ + $$files($$MAYO_DIR/src/base/*.cpp) \ + $$files($$MAYO_DIR/src/io_dxf/*.cpp) \ + $$files($$MAYO_DIR/src/io_image/*.cpp) \ + $$files($$MAYO_DIR/src/io_occ/*.cpp) \ + $$files($$MAYO_DIR/src/io_off/*.cpp) \ + $$files($$MAYO_DIR/src/io_ply/*.cpp) \ + $$files($$MAYO_DIR/src/graphics/*.cpp) \ + $$files($$MAYO_DIR/src/gui/*.cpp) \ + $$files($$MAYO_DIR/src/measure/*.cpp) \ + $$files($$MAYO_DIR/src/app/*.cpp) \ \ - src/3rdparty/fmt/src/format.cc \ + $$MAYO_DIR/src/3rdparty/fmt/src/format.cc \ win32:lessThan(QT_MAJOR_VERSION, 6) { QT += winextras - HEADERS += $$files(src/app/windows/*.h) - SOURCES += $$files(src/app/windows/*.cpp) + HEADERS += $$files($$MAYO_DIR/src/app/windows/*.h) + SOURCES += $$files($$MAYO_DIR/src/app/windows/*.cpp) } -FORMS += $$files(src/app/*.ui) +FORMS += $$files($$MAYO_DIR/src/app/*.ui) -RESOURCES += mayo.qrc -RC_ICONS = images/appicon.ico +RESOURCES += $$MAYO_DIR/mayo.qrc +RC_ICONS = $$MAYO_DIR/images/appicon.ico OTHER_FILES += \ - README.md \ - .github/workflows/ci_linux.yml \ - .github/workflows/ci_linux_arm.yml \ - .github/workflows/ci_macos.yml \ - .github/workflows/ci_windows.yml \ - images/credits.txt \ - scripts/bump-version.rb \ + $$MAYO_DIR/README.md \ + $$MAYO_DIR/.github/workflows/ci_linux.yml \ + $$MAYO_DIR/.github/workflows/ci_linux_arm.yml \ + $$MAYO_DIR/.github/workflows/ci_macos.yml \ + $$MAYO_DIR/.github/workflows/ci_windows.yml \ + $$MAYO_DIR/images/credits.txt \ + $$MAYO_DIR/scripts/bump-version.rb \ # Embed qtbase_*.qm files as a resource qtBaseQmRes.files = \ @@ -229,26 +231,26 @@ versionAtLeast(OCC_VERSION_STR, 7.4.0) { } } else { SOURCES -= \ - src/io_occ/io_occ_base_mesh.cpp \ - src/io_occ/io_occ_gltf_reader.cpp \ - src/io_occ/io_occ_obj_reader.cpp + $$MAYO_DIR/src/io_occ/io_occ_base_mesh.cpp \ + $$MAYO_DIR/src/io_occ/io_occ_gltf_reader.cpp \ + $$MAYO_DIR/src/io_occ/io_occ_obj_reader.cpp message(glTF reader disabled because OpenCascade < v7.4) message(OBJ reader disabled because OpenCascade < v7.4) } !versionAtLeast(OCC_VERSION_STR, 7.5.0) { - SOURCES -= src/io_occ/io_occ_gltf_writer.cpp + SOURCES -= $$MAYO_DIR/src/io_occ/io_occ_gltf_writer.cpp message(glTF writer disabled because OpenCascade < v7.5) } !versionAtLeast(OCC_VERSION_STR, 7.6.0) { - SOURCES -= src/io_occ/io_occ_obj_writer.cpp + SOURCES -= $$MAYO_DIR/src/io_occ/io_occ_obj_writer.cpp message(OBJ writer disabled because OpenCascade < v7.6) } !versionAtLeast(OCC_VERSION_STR, 7.7.0) { - SOURCES -= src/io_occ/io_occ_vrml_reader.cpp + SOURCES -= $$MAYO_DIR/src/io_occ/io_occ_vrml_reader.cpp message(VRML reader disabled because OpenCascade < v7.7) } @@ -266,8 +268,8 @@ isEmpty(ASSIMP_INC_DIR) | isEmpty(ASSIMP_LIB_DIR) { } defined(ASSIMP_IS_ON, var) { - HEADERS += $$files(src/io_assimp/*.h) - SOURCES += $$files(src/io_assimp/*.cpp) + HEADERS += $$files($$MAYO_DIR/src/io_assimp/*.h) + SOURCES += $$files($$MAYO_DIR/src/io_assimp/*.cpp) ASSIMP_VERSION_FILE_CONTENTS = $$cat($$ASSIMP_INC_DIR/version.h, lines) ASSIMP_aiGetVersionPatch = $$find(ASSIMP_VERSION_FILE_CONTENTS, aiGetVersionPatch) @@ -294,8 +296,8 @@ isEmpty(GMIO_ROOT) { GMIO_BIN_SUFFIX = } - HEADERS += $$files(src/io_gmio/*.h) - SOURCES += $$files(src/io_gmio/*.cpp) + HEADERS += $$files($$MAYO_DIR/src/io_gmio/*.h) + SOURCES += $$files($$MAYO_DIR/src/io_gmio/*.cpp) INCLUDEPATH += $$GMIO_ROOT/include LIBS += -L$$GMIO_ROOT/lib -lgmio_static -lzlibstatic @@ -305,7 +307,7 @@ isEmpty(GMIO_ROOT) { # Unit tests CONFIG(withtests) { - include(tests/tests.pri) + include($$MAYO_DIR/tests/tests.pri) DEFINES += MAYO_WITH_TESTS } diff --git a/opencascade.pri b/scripts/qmake/opencascade.pri similarity index 100% rename from opencascade.pri rename to scripts/qmake/opencascade.pri diff --git a/src/app/version.h.in b/scripts/qmake/version.h.in similarity index 100% rename from src/app/version.h.in rename to scripts/qmake/version.h.in diff --git a/version.pri b/scripts/qmake/version.pri similarity index 80% rename from version.pri rename to scripts/qmake/version.pri index 83a6fb1e..263b6ecd 100644 --- a/version.pri +++ b/scripts/qmake/version.pri @@ -26,7 +26,7 @@ QMAKE_TARGET_PRODUCT = Mayo QMAKE_TARGET_COMPANY = Fougue # Generate version file -QMAKE_SUBSTITUTES += $$PWD/src/app/version.h.in +QMAKE_SUBSTITUTES += $$PWD/version.h.in -INCLUDEPATH += $$OUT_PWD/src/app # To allow inclusion as "version.h" from source code -OTHER_FILES += $$PWD/src/app/version.h.in +INCLUDEPATH += $$OUT_PWD # To allow inclusion as "version.h" from source code +OTHER_FILES += $$PWD/version.h.in From 47c4535255078c3b13a4d632100e8553aba683dd Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 22 Feb 2024 18:15:10 +0100 Subject: [PATCH 020/101] Scripts: handle README.md and CMakeList.txt in bump-version.rb --- scripts/bump-version.rb | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/scripts/bump-version.rb b/scripts/bump-version.rb index dadfb5e5..1af15b2e 100644 --- a/scripts/bump-version.rb +++ b/scripts/bump-version.rb @@ -23,10 +23,28 @@ puts "Major: #{major}" puts "Minor: #{minor}" puts "Patch: #{patch}" -script_dir_name = File.expand_path(File.dirname(__FILE__)) +scripts_dir_name = File.expand_path(File.dirname(__FILE__)) -# ../version.pri -path_version_pri = "#{script_dir_name}/../version.pri" +# ../README.md +path_README_md = "#{scripts_dir_name}/../README.md" +README_md = File.open(path_README_md, "r").read +README_md.sub!(/(img.shields.io\/badge\/version-v)\d+\.\d+.\d+/, "\\1#{major}.#{minor}.#{patch}") +README_md.sub!(/(Mayo_VersionMinor\s+)\d+/, "\\1#{minor}") +README_md.sub!(/(Mayo_VersionPatch\s+)\d+/, "\\1#{patch}") +File.open(path_README_md, "w").write(README_md) +puts "Bumped #{path_README_md}" + +# ../CMakeLists.txt +path_CMakeLists_txt = "#{scripts_dir_name}/../CMakeLists.txt" +CMakeLists_txt = File.open(path_CMakeLists_txt, "r").read +CMakeLists_txt.sub!(/(Mayo_VersionMajor\s+)\d+/, "\\1#{major}") +CMakeLists_txt.sub!(/(Mayo_VersionMinor\s+)\d+/, "\\1#{minor}") +CMakeLists_txt.sub!(/(Mayo_VersionPatch\s+)\d+/, "\\1#{patch}") +File.open(path_CMakeLists_txt, "w").write(CMakeLists_txt) +puts "Bumped #{path_CMakeLists_txt}" + +# qmake/version.pri +path_version_pri = "#{scripts_dir_name}/qmake/version.pri" version_pri = File.open(path_version_pri, "r").read version_pri.sub!(/(MAYO_VERSION_MAJ\s*=\s*)\d+/, "\\1#{major}") version_pri.sub!(/(MAYO_VERSION_MIN\s*=\s*)\d+/, "\\1#{minor}") From 8fa2fa4ad0ba22315badbf0f33ef9168920cf81e Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 22 Feb 2024 18:21:32 +0100 Subject: [PATCH 021/101] CMake: simplify assimp and tests steps --- .github/workflows/ci_linux_arm.yml | 10 +++--- CMakeLists.txt | 57 ++++++++++++------------------ 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ci_linux_arm.yml b/.github/workflows/ci_linux_arm.yml index 0e03849b..2d5a3d33 100644 --- a/.github/workflows/ci_linux_arm.yml +++ b/.github/workflows/ci_linux_arm.yml @@ -6,7 +6,8 @@ jobs: build-linux-arm: strategy: matrix: - arch: [armv7, aarch64] + #arch: [armv7, aarch64] + arch: [aarch64] runs-on: ubuntu-latest @@ -45,12 +46,13 @@ jobs: cd mayo echo GIT_BRANCH_NAME=$GIT_BRANCH_NAME # Fixes "fatal: detected dubious ownership in repository at '/home/runner/work/mayo/mayo'" - git config --global --add safe.directory `pwd` + #git config --global --add safe.directory `pwd` + git config --global --add safe.directory /home/runner/work/mayo/mayo git checkout $GIT_BRANCH_NAME cd .. - mkdir build-mayo - cd build-mayo + mkdir build + cd build cmake --version cmake .. \ -DMayo_BuildTests=ON \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 5afc6de2..7ec398cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ set(Mayo_VersionRevNum 0) find_package(Git) if(Git_FOUND) # Maybe use preferrably add_custom_command() with PRE_BUILD event? + message(STATUS "GIT_EXECUTABLE=${GIT_EXECUTABLE}") execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} @@ -41,6 +42,15 @@ if(Git_FOUND) # Fix newline character at the end of Mayo_VersionCommit and Mayo_VersionRevNum variables string(REGEX REPLACE "\n$" "" Mayo_VersionCommit "${Mayo_VersionCommit}") string(REGEX REPLACE "\n$" "" Mayo_VersionRevNum "${Mayo_VersionRevNum}") + # execute_process() may have failed, so make sure Mayo_Version[Commit/RevNum] are not empty strings + # Note: execute_process() could fail in the typical case where Mayo source code was extracted + # from ZIP archive file(ie not via "git clone") + if(Mayo_VersionCommit STREQUAL "") + set(Mayo_VersionCommit 0) + endif() + if(Mayo_VersionRevNum STREQUAL "") + set(Mayo_VersionRevNum 0) + endif() endif() configure_file(${PROJECT_SOURCE_DIR}/src/app/version.h.cmake app/version.h @ONLY) @@ -228,33 +238,16 @@ file( ########## if(Mayo_BuildTests) - list( - APPEND Mayo_HeaderFiles - ${PROJECT_SOURCE_DIR}/tests/test_app.h - ${PROJECT_SOURCE_DIR}/tests/test_base.h - ${PROJECT_SOURCE_DIR}/tests/test_measure.h - ) - - list( - APPEND Mayo_SourceFiles - ${PROJECT_SOURCE_DIR}/tests/runtests.cpp - ${PROJECT_SOURCE_DIR}/tests/test_app.cpp - ${PROJECT_SOURCE_DIR}/tests/test_base.cpp - ${PROJECT_SOURCE_DIR}/tests/test_measure.cpp - ) + file(GLOB MayoTests_HeaderFiles ${PROJECT_SOURCE_DIR}/tests/*.h) + file(GLOB MayoTests_SourceFiles ${PROJECT_SOURCE_DIR}/tests/*.cpp) + list(APPEND Mayo_HeaderFiles ${MayoTests_HeaderFiles}) + list(APPEND Mayo_SourceFiles ${MayoTests_SourceFiles}) list(APPEND Mayo_CompileDefinitions MAYO_WITH_TESTS) list(APPEND Mayo_LinkLibraries Qt${QT_VERSION_MAJOR}::Test) - file( - GLOB Mayo_InputTestFiles - ${PROJECT_SOURCE_DIR}/tests/inputs/*.* - ) - file( - COPY ${Mayo_InputTestFiles} - DESTINATION ${CMAKE_BINARY_DIR}/tests/inputs - ) - + file(GLOB MayoTests_InputFiles ${PROJECT_SOURCE_DIR}/tests/inputs/*.*) + file(COPY ${MayoTests_InputFiles} DESTINATION ${CMAKE_BINARY_DIR}/tests/inputs) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/tests/outputs) endif() @@ -322,7 +315,7 @@ if(WIN32) # Add OpenCASCADE library directories # Note: QtCreator option Run->Add_build_library_search_to_PATH will add to PATH env variable # the contents of Mayo_LinkDirectories variable. For convenience, let's add also the - # directories containing the DLLs + # directories containing DLLs list( APPEND Mayo_LinkDirectories ${OpenCASCADE_BINARY_DIR} @@ -443,16 +436,12 @@ endif() if(assimp_FOUND) message(STATUS "assimp version ${assimp_VERSION}") - list( - APPEND Mayo_HeaderFiles - ${PROJECT_SOURCE_DIR}/src/io_assimp/io_assimp.h - ${PROJECT_SOURCE_DIR}/src/io_assimp/io_assimp_reader.h - ) - list( - APPEND Mayo_SourceFiles - ${PROJECT_SOURCE_DIR}/src/io_assimp/io_assimp.cpp - ${PROJECT_SOURCE_DIR}/src/io_assimp/io_assimp_reader.cpp - ) + + file(GLOB MayoPluginAssimp_HeaderFiles ${PROJECT_SOURCE_DIR}/src/io_assimp/*.h) + file(GLOB MayoPluginAssimp_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_assimp/*.cpp) + list(APPEND Mayo_HeaderFiles ${MayoPluginAssimp_HeaderFiles}) + list(APPEND Mayo_SourceFiles ${MayoPluginAssimp_SourceFiles}) + list(APPEND Mayo_LinkLibraries ${ASSIMP_LIBRARIES}) list(APPEND Mayo_CompileDefinitions HAVE_ASSIMP) From 9e340cd7c3543f756766d6c31e645f54fa986abc Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 23 Feb 2024 10:26:13 +0100 Subject: [PATCH 022/101] CI: add archarm/armv7 build for Linux ARM --- .github/workflows/ci_linux_arm.yml | 74 ++++++++++++++++++------------ .github/workflows/ci_windows.yml | 1 + 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci_linux_arm.yml b/.github/workflows/ci_linux_arm.yml index 2d5a3d33..43a2652f 100644 --- a/.github/workflows/ci_linux_arm.yml +++ b/.github/workflows/ci_linux_arm.yml @@ -6,8 +6,12 @@ jobs: build-linux-arm: strategy: matrix: - #arch: [armv7, aarch64] - arch: [aarch64] + include: + - arch: aarch64 + distro: ubuntu22.04 + - arch: armv7 + distro: archarm_latest + runs-on: ubuntu-latest @@ -22,19 +26,37 @@ jobs: uses: uraimo/run-on-arch-action@v2 with: arch: ${{matrix.arch}} - distro: ubuntu22.04 + distro: ${{matrix.distro}} githubToken: ${{github.token}} # Not required, but speeds up builds install: | - apt-get -y update - apt-get -y install xvfb # Needed to run unit tests - apt-get -y install cmake - apt-get -y install git - apt-get -y install g++ - apt-get -y install qtbase5-dev libqt5svg5-dev - apt-get -y install libxcb-cursor0 # Dependencies for "xcb" Qt plugin(actually needed for Qt6) - apt-get -y install libocct-data-exchange-dev libocct-draw-dev occt-misc - apt-get -y install libtbb2-dev libxi-dev # Dependencies for OpenCascade v7.5 - apt-get -y install libassimp-dev + case "${{ matrix.distro }}" in + ubuntu*) + apt-get -y update + apt-get -y install xvfb # Needed to run unit tests + apt-get -y install cmake + apt-get -y install git + apt-get -y install g++ + apt-get -y install qtbase5-dev libqt5svg5-dev + apt-get -y install libxcb-cursor0 # Dependencies for "xcb" Qt plugin(actually needed for Qt6) + apt-get -y install libocct-data-exchange-dev libocct-draw-dev occt-misc + apt-get -y install libtbb2-dev libxi-dev # Dependencies for OpenCascade v7.5 + apt-get -y install libassimp-dev + ;; + archarm*) + # See https://github.com/moby/buildkit/issues/1267#issuecomment-769880570 + sed -i -e 's~#IgnorePkg.*~IgnorePkg = filesystem~g' '/etc/pacman.conf' + pacman -Syu --noconfirm + pacman -Sy --noconfirm xorg-server-xvfb + pacman -Sy --noconfirm make cmake + pacman -Sy --noconfirm git + pacman -Sy --noconfirm gcc + pacman -Sy --noconfirm qt5-base qt5-svg + pacman -Sy --noconfirm xcb-util-cursor + pacman -Sy --noconfirm opencascade + pacman -Sy --noconfirm libxi + pacman -Sy --noconfirm assimp + ;; + esac env: | DISPLAY: :0 @@ -42,17 +64,18 @@ jobs: RUN_ARCH_NAME: ${{matrix.arch}} run: | - git clone https://github.com/fougue/mayo.git + git clone --quiet https://github.com/fougue/mayo.git cd mayo - echo GIT_BRANCH_NAME=$GIT_BRANCH_NAME + # Fixes "fatal: detected dubious ownership in repository at '/home/runner/work/mayo/mayo'" - #git config --global --add safe.directory `pwd` - git config --global --add safe.directory /home/runner/work/mayo/mayo + git config --global --add safe.directory `realpath $PWD/..` + echo GIT_BRANCH_NAME=$GIT_BRANCH_NAME git checkout $GIT_BRANCH_NAME cd .. mkdir build cd build + cmake --version cmake .. \ -DMayo_BuildTests=ON \ @@ -61,16 +84,7 @@ jobs: --config Release \ --parallel `nproc` - # - # Execute Unit Tests - # Note: for some unknown reason unit tests are crashing for armv7(on very first test) - # so just execute them for aarch64 - # - if [ $RUN_ARCH_NAME = 'aarch64' ]; then - # Start X11 display server - Xvfb $DISPLAY -screen 0 1280x1024x24 & - sleep 5s - ./mayo --runtests - else - echo "NOTE: unit tests skipped, they need to be fixed for this architecture" - fi + # Start X11 display server + Xvfb $DISPLAY -screen 0 1280x1024x24 & + sleep 5s + ./mayo --runtests diff --git a/.github/workflows/ci_windows.yml b/.github/workflows/ci_windows.yml index 42c97700..78527c47 100644 --- a/.github/workflows/ci_windows.yml +++ b/.github/workflows/ci_windows.yml @@ -21,6 +21,7 @@ jobs: occ_zip_sha256: 'beb30920efe5e5384d500d43dcb8ba1f476a76bb65b268b1af32d807b697bdee' runs-on: windows-latest + name: build-win-msvc-occt${{matrix.occ_version}} steps: - name: Checkout From 035de7142360a1f509eede9439ba9b4edb3c9474 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 26 Feb 2024 12:23:54 +0100 Subject: [PATCH 023/101] CI: add fedora/aarch64 build for Linux ARM --- .github/workflows/ci_linux_arm.yml | 26 +++++++++++++++++++++++++- tests/test_base.cpp | 1 + 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_linux_arm.yml b/.github/workflows/ci_linux_arm.yml index 43a2652f..d9230349 100644 --- a/.github/workflows/ci_linux_arm.yml +++ b/.github/workflows/ci_linux_arm.yml @@ -8,7 +8,9 @@ jobs: matrix: include: - arch: aarch64 - distro: ubuntu22.04 + distro: ubuntu_latest + - arch: aarch64 + distro: fedora_latest - arch: armv7 distro: archarm_latest @@ -42,6 +44,17 @@ jobs: apt-get -y install libtbb2-dev libxi-dev # Dependencies for OpenCascade v7.5 apt-get -y install libassimp-dev ;; + fedora*) + dnf -y update + dnf -y install xorg-x11-server-Xvfb + dnf -y install make cmake + dnf -y install git + dnf -y install gcc-c++ + dnf -y install qt5-qtbase qt5-qtsvg + dnf -y install xcb-util-cursor + dnf -y install opencascade-devel + dnf -y install assimp assimp-devel + ;; archarm*) # See https://github.com/moby/buildkit/issues/1267#issuecomment-769880570 sed -i -e 's~#IgnorePkg.*~IgnorePkg = filesystem~g' '/etc/pacman.conf' @@ -84,7 +97,18 @@ jobs: --config Release \ --parallel `nproc` + # + # Execute Unit Tests + # Note: for some unknown reason unit tests are crashing for armv7(on very first test) + # so just execute them for aarch64 + # + if [ $RUN_ARCH_NAME = 'aarch64' ]; then + # Start X11 display server Xvfb $DISPLAY -screen 0 1280x1024x24 & sleep 5s ./mayo --runtests + + else + echo "NOTE: unit tests skipped, they need to be fixed for this architecture" + fi diff --git a/tests/test_base.cpp b/tests/test_base.cpp index 2c1d0de7..0435f183 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -249,6 +249,7 @@ void TestBase::Application_test() } QCOMPARE(app->documentCount(), 0); + } void TestBase::DocumentRefCount_test() From 4010d8faa7111e587f93dc83ddae89b5d98e254e Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 26 Feb 2024 18:13:19 +0100 Subject: [PATCH 024/101] CMake: on Windows set OpenCASCADE compile definitions HAVE_RAPIDJSON --- CMakeLists.txt | 43 ++++++++++++++++++++------ scripts/gen-cmake-opencascade-vars.bat | 6 ++++ tests/test_base.cpp | 4 +++ 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ec398cf..80ea0061 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -260,12 +260,23 @@ if(WIN32) # See https://dev.opencascade.org/content/3rdparty-hard-coded-paths-when-building-against-opencascade set(OpenCASCADE_DIR CACHE PATH "Path where is located OpenCASCADE env.bat file") + + # OpenCASCADE_DIR may actually points to opencascade/cmake + if(NOT EXISTS ${OpenCASCADE_DIR}/env.bat) + cmake_path(GET OpenCASCADE_DIR PARENT_PATH OpenCASCADE_DIR_PARENT_PATH) + if(EXISTS ${OpenCASCADE_DIR_PARENT_PATH}/env.bat) + set(OpenCASCADE_DIR ${OpenCASCADE_DIR_PARENT_PATH}) + endif() + endif() + + # Generate file "Mayo_OpenCascadeEnv.cmake" execute_process( COMMAND cmd /c ${PROJECT_SOURCE_DIR}/scripts/gen-cmake-opencascade-vars.bat "${OpenCASCADE_DIR}" OUTPUT_FILE Mayo_OpenCascadeEnv.cmake ) - include(${CMAKE_BINARY_DIR}/Mayo_OpenCascadeEnv.cmake) + + # Set OpenCASCADE variables if(OpenCASCADE_FOUND) # Set variables for OpenCASCADE version file(READ ${OpenCASCADE_INCLUDE_DIR}/Standard_Version.hxx OpenCascade_FileVersionHxx) @@ -280,12 +291,26 @@ if(WIN32) # Add OpenCASCADE include directory list(APPEND Mayo_IncludeDirectories ${OpenCASCADE_INCLUDE_DIR}) - # Set OpenCASACADE_3RDPARTY_BINARY_DIRS variable that will contain the OpenCASCADE + # Add OpenCASCADE "HAVE_RAPIDJSON" compile flags + if(OpenCASCADE_HAVE_RAPIDJSON STREQUAL "true") + list(APPEND Mayo_CompileDefinitions HAVE_RAPIDJSON) + elseif(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) + file(GLOB OpenCASCADE_CompileDefsCMakeFiles ${OpenCASCADE_DIR}/cmake/OpenCASCADECompileDefinitions*.cmake) + foreach(CMakeFile ${OpenCASCADE_CompileDefsCMakeFiles}) + file(READ "${CMakeFile}" CMakeFileContents) + if(CMakeFileContents MATCHES ":HAVE_RAPIDJSON>") + list(APPEND Mayo_CompileDefinitions HAVE_RAPIDJSON) + break() + endif() + endforeach() + endif() + + # Set OpenCASCADE_3RDPARTY_BINARY_DIRS variable that will contain the OpenCASCADE # 3rd-party binary directories(ie containing DLL files) - set(OpenCASACADE_3RDPARTY_BINARY_DIRS) + set(OpenCASCADE_3RDPARTY_BINARY_DIRS) if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) list( - APPEND OpenCASACADE_3RDPARTY_BINARY_DIRS + APPEND OpenCASCADE_3RDPARTY_BINARY_DIRS ${TCL_DIR} ${TK_DIR} ${FREETYPE_DIR} @@ -297,19 +322,19 @@ if(WIN32) ) else() if(Mayo_TargetArchBitSize STREQUAL "32") - list(APPEND OpenCASACADE_3RDPARTY_BINARY_DIRS ${CSF_OPT_BIN32}) + list(APPEND OpenCASCADE_3RDPARTY_BINARY_DIRS ${CSF_OPT_BIN32}) else() - list(APPEND OpenCASACADE_3RDPARTY_BINARY_DIRS ${CSF_OPT_BIN64}) + list(APPEND OpenCASCADE_3RDPARTY_BINARY_DIRS ${CSF_OPT_BIN64}) endif() # Remove Qt binary path probably added in CSF_OPT_BIN(32/64) variable set(OpenCASCADE_QT_BINDIR) - foreach(BINDIR ${OpenCASACADE_3RDPARTY_BINARY_DIRS}) + foreach(BINDIR ${OpenCASCADE_3RDPARTY_BINARY_DIRS}) if(BINDIR MATCHES "qt5") set(OpenCASCADE_QT_BINDIR ${BINDIR}) endif() endforeach() - list(REMOVE_ITEM OpenCASACADE_3RDPARTY_BINARY_DIRS ${OpenCASCADE_QT_BINDIR}) + list(REMOVE_ITEM OpenCASCADE_3RDPARTY_BINARY_DIRS ${OpenCASCADE_QT_BINDIR}) endif() # Add OpenCASCADE library directories @@ -320,7 +345,7 @@ if(WIN32) APPEND Mayo_LinkDirectories ${OpenCASCADE_BINARY_DIR} ${OpenCASCADE_LIBRARY_DIR} - ${OpenCASACADE_3RDPARTY_BINARY_DIRS} + ${OpenCASCADE_3RDPARTY_BINARY_DIRS} ) else() message(WARNING "OpenCASCADE not found from this location OpenCASCADE_DIR='${OpenCASCADE_DIR}' ") diff --git a/scripts/gen-cmake-opencascade-vars.bat b/scripts/gen-cmake-opencascade-vars.bat index 670bd54a..7c344c99 100644 --- a/scripts/gen-cmake-opencascade-vars.bat +++ b/scripts/gen-cmake-opencascade-vars.bat @@ -9,6 +9,10 @@ set CSF_OCCTIncludePath= set CSF_OCCTBinPath= set CSF_OCCTLibPath= set CSF_OCCTResourcePath= +set CSF_OPT_BIN32= +set CSF_OPT_BIN32D= +set CSF_OPT_BIN64= +set CSF_OPT_BIN64D= set TCL_DIR= set TK_DIR= set FREETYPE_DIR= @@ -17,6 +21,7 @@ set FFMPEG_DIR= set TBB_DIR= set OPENVR_DIR= set JEMALLOC_DIR= +set HAVE_RAPIDJSON= call "%_OPENCASCADE_ROOTDIR_%\env.bat" @@ -40,5 +45,6 @@ echo set(CSF_OPT_BIN32 "%CSF_OPT_BIN32:\=/%") echo set(CSF_OPT_BIN32D "%CSF_OPT_BIN32D:\=/%") echo set(CSF_OPT_BIN64 "%CSF_OPT_BIN64:\=/%") echo set(CSF_OPT_BIN64D "%CSF_OPT_BIN64D:\=/%") +echo set(OpenCASCADE_HAVE_RAPIDJSON "%HAVE_RAPIDJSON%") REM TODO What about variables CSF_SHMessage, CSF_MDTVTexturesDirectory, ... diff --git a/tests/test_base.cpp b/tests/test_base.cpp index 0435f183..2be649ad 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -606,15 +606,19 @@ void TestBase::IO_bugGitHub166_test_data() #if OCC_VERSION_HEX >= 0x070400 QTest::newRow("OBJ->PLY") << "tests/inputs/cube.obj" << "tests/outputs/cube.ply" << IO::Format_PLY; QTest::newRow("OBJ->STL") << "tests/inputs/cube.obj" << "tests/outputs/cube.stl" << IO::Format_STL; +# ifdef HAVE_RAPIDJSON QTest::newRow("glTF->PLY") << "tests/inputs/cube.gltf" << "tests/outputs/cube.ply" << IO::Format_PLY; QTest::newRow("glTF->STL") << "tests/inputs/cube.gltf" << "tests/outputs/cube.stl" << IO::Format_STL; +# endif #endif #if OCC_VERSION_HEX >= 0x070600 QTest::newRow("PLY->OBJ") << "tests/inputs/cube.ply" << "tests/outputs/cube.obj" << IO::Format_OBJ; QTest::newRow("STL->OBJ") << "tests/inputs/cube.stla" << "tests/outputs/cube.obj" << IO::Format_OBJ; +# ifdef HAVE_RAPIDJSON QTest::newRow("glTF->OBJ") << "tests/inputs/cube.gltf" << "tests/outputs/cube.obj" << IO::Format_OBJ; QTest::newRow("OBJ->glTF") << "tests/inputs/cube.obj" << "tests/outputs/cube.glTF" << IO::Format_GLTF; +# endif #endif } From 8ebab10bedb7370b39c568188f4e4a3cb029a1d1 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Tue, 27 Feb 2024 18:02:49 +0100 Subject: [PATCH 025/101] CMake: fix creation of OpenCascadeEnv.cmake file + easier Qt configuration --- CMakeLists.txt | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 80ea0061..700b3658 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,8 @@ project( set(Mayo_Copyright "Copyright @ 2016-present, Fougue Ltd., All Rights Reserved") mark_as_advanced(FORCE Mayo_Copyright) +set(CMAKE_EXECUTE_PROCESS_COMMAND_ECHO STDOUT) + # Find git commit hash and revision number set(Mayo_VersionCommit 0) set(Mayo_VersionRevNum 0) @@ -73,19 +75,20 @@ option(Mayo_BuildPluginAssimp "Build plugin to import/export mesh files supporte # Qt ########## -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) -if(QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets Test) - if(WIN32) +find_package(QT NAMES Qt6 Qt5 REQUIRED) +if(QT_FOUND) + set("Qt${QT_VERSION_MAJOR}_DIR" "${QT_DIR}") + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widgets Test) + if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) find_package(Qt5 REQUIRED COMPONENTS WinExtras) + elseif(QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 REQUIRED COMPONENTS OpenGLWidgets) endif() -elseif(QT_VERSION_MAJOR EQUAL 6) - find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Test OpenGLWidgets) -endif() -message(STATUS "Qt version ${QT_VERSION}") -if(QT_VERSION VERSION_LESS 5.14) - message(FATAL_ERROR "Qt >= 5.14 is required but detected version is ${QT_VERSION}") + message(STATUS "Qt version ${QT_VERSION}") + if(QT_VERSION VERSION_LESS 5.14) + message(FATAL_ERROR "Qt >= 5.14 is required but detected version is ${QT_VERSION}") + endif() endif() #qt_standard_project_setup() @@ -270,11 +273,13 @@ if(WIN32) endif() # Generate file "Mayo_OpenCascadeEnv.cmake" - execute_process( - COMMAND cmd /c ${PROJECT_SOURCE_DIR}/scripts/gen-cmake-opencascade-vars.bat "${OpenCASCADE_DIR}" - OUTPUT_FILE Mayo_OpenCascadeEnv.cmake - ) - include(${CMAKE_BINARY_DIR}/Mayo_OpenCascadeEnv.cmake) + if(EXISTS "${OpenCASCADE_DIR}/env.bat") + execute_process( + COMMAND cmd /c ${PROJECT_SOURCE_DIR}/scripts/gen-cmake-opencascade-vars.bat "${OpenCASCADE_DIR}" + OUTPUT_FILE ${CMAKE_BINARY_DIR}/Mayo_OpenCascadeEnv.cmake + ) + include(${CMAKE_BINARY_DIR}/Mayo_OpenCascadeEnv.cmake) + endif() # Set OpenCASCADE variables if(OpenCASCADE_FOUND) @@ -338,7 +343,7 @@ if(WIN32) endif() # Add OpenCASCADE library directories - # Note: QtCreator option Run->Add_build_library_search_to_PATH will add to PATH env variable + # Note: QtCreator option "Run->Add build library search to PATH" will add to PATH env variable # the contents of Mayo_LinkDirectories variable. For convenience, let's add also the # directories containing DLLs list( From 6e0fc231aefc3bb727f1f48460d86cb17cddf4cf Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Wed, 28 Feb 2024 17:51:59 +0100 Subject: [PATCH 026/101] CMake: add new option "Mayo_PostBuildCopyRuntimeDLLs" Relates to GitHub #53 --- CMakeLists.txt | 165 ++++++++++++++++++------------------- cmake/OpenCascadeWin.cmake | 142 +++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 87 deletions(-) create mode 100644 cmake/OpenCascadeWin.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 700b3658..f5011905 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,24 @@ math(EXPR Mayo_TargetArchBitSize "8 * ${CMAKE_SIZEOF_VOID_P}") option(Mayo_BuildTests "Build Mayo testing suite" OFF) option(Mayo_BuildPluginAssimp "Build plugin to import/export mesh files supported by Assimp" OFF) +if(WIN32) + set(Mayo_PostBuildCopyRuntimeDLLs_DefaultValue OFF) + if(CMAKE_GENERATOR MATCHES "Visual Studio") + set(Mayo_PostBuildCopyRuntimeDLLs_DefaultValue ON) + endif() + + message(STATUS "Mayo_PostBuildCopyRuntimeDLLs_DefaultValue=${Mayo_PostBuildCopyRuntimeDLLs_DefaultValue}") + option( + Mayo_PostBuildCopyRuntimeDLLs + "\ +Add post build step to copy all runtime DLLs alongside Mayo executable.\n\ +This simplifies running Mayo from eg Visual Studio IDE\ +" + ${Mayo_PostBuildCopyRuntimeDLLs_DefaultValue} + ) + +endif() + # TODO # option(Mayo_BuildPluginGmio "Build plugin to import/export mesh files supported by gmio" OFF) @@ -85,6 +103,10 @@ if(QT_FOUND) find_package(Qt6 REQUIRED COMPONENTS OpenGLWidgets) endif() + if(Mayo_PostBuildCopyRuntimeDLLs) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Svg) + endif() + message(STATUS "Qt version ${QT_VERSION}") if(QT_VERSION VERSION_LESS 5.14) message(FATAL_ERROR "Qt >= 5.14 is required but detected version is ${QT_VERSION}") @@ -151,13 +173,21 @@ endif() set(Mayo_LinkLibraries) set(Mayo_LinkDirectories) -if(QT_VERSION_MAJOR EQUAL 5) - list(APPEND Mayo_LinkLibraries Qt5::Core Qt5::Gui Qt5::Widgets) - if(WIN32) - list(APPEND Mayo_LinkLibraries Qt5::WinExtras) - endif() +list( + APPEND Mayo_LinkLibraries + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets +) + +if(Mayo_PostBuildCopyRuntimeDLLs) + list(APPEND Mayo_LinkLibraries Qt${QT_VERSION_MAJOR}::Svg) +endif() + +if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) + list(APPEND Mayo_LinkLibraries Qt5::WinExtras) elseif(QT_VERSION_MAJOR EQUAL 6) - list(APPEND Mayo_LinkLibraries Qt6::Core Qt6::Gui Qt6::Widgets Qt6::OpenGLWidgets) + list(APPEND Mayo_LinkLibraries Qt6::OpenGLWidgets) endif() if(MSVC) @@ -261,87 +291,11 @@ endif() if(WIN32) # Can't use find_package(OpenCASCADE) as OpenCASCADEConfig.cmake is broken for Windows platform # See https://dev.opencascade.org/content/3rdparty-hard-coded-paths-when-building-against-opencascade - - set(OpenCASCADE_DIR CACHE PATH "Path where is located OpenCASCADE env.bat file") - - # OpenCASCADE_DIR may actually points to opencascade/cmake - if(NOT EXISTS ${OpenCASCADE_DIR}/env.bat) - cmake_path(GET OpenCASCADE_DIR PARENT_PATH OpenCASCADE_DIR_PARENT_PATH) - if(EXISTS ${OpenCASCADE_DIR_PARENT_PATH}/env.bat) - set(OpenCASCADE_DIR ${OpenCASCADE_DIR_PARENT_PATH}) - endif() - endif() - - # Generate file "Mayo_OpenCascadeEnv.cmake" - if(EXISTS "${OpenCASCADE_DIR}/env.bat") - execute_process( - COMMAND cmd /c ${PROJECT_SOURCE_DIR}/scripts/gen-cmake-opencascade-vars.bat "${OpenCASCADE_DIR}" - OUTPUT_FILE ${CMAKE_BINARY_DIR}/Mayo_OpenCascadeEnv.cmake - ) - include(${CMAKE_BINARY_DIR}/Mayo_OpenCascadeEnv.cmake) - endif() - - # Set OpenCASCADE variables - if(OpenCASCADE_FOUND) - # Set variables for OpenCASCADE version - file(READ ${OpenCASCADE_INCLUDE_DIR}/Standard_Version.hxx OpenCascade_FileVersionHxx) - string(REGEX MATCH "define[ \t]+OCC_VERSION_MAJOR[ \t]+([0-9]+)" OpenCASCADE_MAJOR_VERSION "${OpenCascade_FileVersionHxx}") - set(OpenCASCADE_MAJOR_VERSION ${CMAKE_MATCH_1}) - string(REGEX MATCH "define[ \t]+OCC_VERSION_MINOR[ \t]+([0-9]+)" OpenCASCADE_MINOR_VERSION "${OpenCascade_FileVersionHxx}") - set(OpenCASCADE_MINOR_VERSION ${CMAKE_MATCH_1}) - string(REGEX MATCH "define[ \t]+OCC_VERSION_MAINTENANCE[ \t]+([0-9]+)" OpenCASCADE_MAINTENANCE_VERSION "${OpenCascade_FileVersionHxx}") - set(OpenCASCADE_MAINTENANCE_VERSION ${CMAKE_MATCH_1}) - set(OpenCASCADE_VERSION "${OpenCASCADE_MAJOR_VERSION}.${OpenCASCADE_MINOR_VERSION}.${OpenCASCADE_MAINTENANCE_VERSION}") - + include(${PROJECT_SOURCE_DIR}/cmake/OpenCascadeWin.cmake) + if (OpenCASCADE_FOUND) # Add OpenCASCADE include directory list(APPEND Mayo_IncludeDirectories ${OpenCASCADE_INCLUDE_DIR}) - # Add OpenCASCADE "HAVE_RAPIDJSON" compile flags - if(OpenCASCADE_HAVE_RAPIDJSON STREQUAL "true") - list(APPEND Mayo_CompileDefinitions HAVE_RAPIDJSON) - elseif(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) - file(GLOB OpenCASCADE_CompileDefsCMakeFiles ${OpenCASCADE_DIR}/cmake/OpenCASCADECompileDefinitions*.cmake) - foreach(CMakeFile ${OpenCASCADE_CompileDefsCMakeFiles}) - file(READ "${CMakeFile}" CMakeFileContents) - if(CMakeFileContents MATCHES ":HAVE_RAPIDJSON>") - list(APPEND Mayo_CompileDefinitions HAVE_RAPIDJSON) - break() - endif() - endforeach() - endif() - - # Set OpenCASCADE_3RDPARTY_BINARY_DIRS variable that will contain the OpenCASCADE - # 3rd-party binary directories(ie containing DLL files) - set(OpenCASCADE_3RDPARTY_BINARY_DIRS) - if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) - list( - APPEND OpenCASCADE_3RDPARTY_BINARY_DIRS - ${TCL_DIR} - ${TK_DIR} - ${FREETYPE_DIR} - ${FREEIMAGE_DIR} - ${FFMPEG_DIR} - ${TBB_DIR} - ${OPENVR_DIR} - ${JEMALLOC_DIR} - ) - else() - if(Mayo_TargetArchBitSize STREQUAL "32") - list(APPEND OpenCASCADE_3RDPARTY_BINARY_DIRS ${CSF_OPT_BIN32}) - else() - list(APPEND OpenCASCADE_3RDPARTY_BINARY_DIRS ${CSF_OPT_BIN64}) - endif() - - # Remove Qt binary path probably added in CSF_OPT_BIN(32/64) variable - set(OpenCASCADE_QT_BINDIR) - foreach(BINDIR ${OpenCASCADE_3RDPARTY_BINARY_DIRS}) - if(BINDIR MATCHES "qt5") - set(OpenCASCADE_QT_BINDIR ${BINDIR}) - endif() - endforeach() - list(REMOVE_ITEM OpenCASCADE_3RDPARTY_BINARY_DIRS ${OpenCASCADE_QT_BINDIR}) - endif() - # Add OpenCASCADE library directories # Note: QtCreator option "Run->Add build library search to PATH" will add to PATH env variable # the contents of Mayo_LinkDirectories variable. For convenience, let's add also the @@ -352,9 +306,7 @@ if(WIN32) ${OpenCASCADE_LIBRARY_DIR} ${OpenCASCADE_3RDPARTY_BINARY_DIRS} ) - else() - message(WARNING "OpenCASCADE not found from this location OpenCASCADE_DIR='${OpenCASCADE_DIR}' ") - endif() + endif() else() find_package(OpenCASCADE REQUIRED) if(OpenCASCADE_FOUND) @@ -401,6 +353,7 @@ if(OpenCASCADE_FOUND) ) if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) + list(APPEND Mayo_LinkLibraries TKDE) list(APPEND Mayo_LinkLibraries TKDEIGES) list(APPEND Mayo_LinkLibraries TKDESTEP) list(APPEND Mayo_LinkLibraries TKDESTL) @@ -553,3 +506,41 @@ set_target_properties( WIN32_EXECUTABLE TRUE MACOSX_BUNDLE TRUE ) + +if(Mayo_PostBuildCopyRuntimeDLLs) + # Copy required Qt plugins + set(QtPluginsDir "${QT_DIR}/../../../plugins") + file(GLOB QtPluginIconEnginesDLLs "${QtPluginsDir}/iconengines/qsvgicon*.dll") + file(GLOB QtPluginImageFormatsDLLs "${QtPluginsDir}/imageformats/qsvg*.dll") + file(GLOB QtPluginPlatformsDLLs "${QtPluginsDir}/platforms/qwindows*.dll") + set(QtPluginsDLLs ${QtPluginIconEnginesDLLs} ${QtPluginImageFormatsDLLs} ${QtPluginPlatformsDLLs}) + foreach(QtPluginDLL ${QtPluginsDLLs}) + cmake_path(GET QtPluginDLL PARENT_PATH QtPluginDLL_Path) + cmake_path(GET QtPluginDLL_Path FILENAME QtPluginDLL_PathName) + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "$/plugins/${QtPluginDLL_PathName}" + ) + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${QtPluginDLL}" "$/plugins/${QtPluginDLL_PathName}" + ) + endforeach() + + # Copy OpenCascade 3rd-party DLLs + foreach(Occ3rdDLL ${OpenCASCADE_3RDPARTY_DLLS}) + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${Occ3rdDLL}" $ + ) + endforeach() + + # Copy runtime DLLs specified with library IMPORTED_LOCATION property + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ $ + COMMAND_EXPAND_LISTS + ) +endif() diff --git a/cmake/OpenCascadeWin.cmake b/cmake/OpenCascadeWin.cmake new file mode 100644 index 00000000..138783c7 --- /dev/null +++ b/cmake/OpenCascadeWin.cmake @@ -0,0 +1,142 @@ +set(OpenCASCADE_DIR CACHE PATH "Path where is located OpenCASCADE env.bat file") + +# OpenCASCADE_DIR may actually points to opencascade/cmake +if(NOT EXISTS ${OpenCASCADE_DIR}/env.bat) + cmake_path(GET OpenCASCADE_DIR PARENT_PATH OpenCASCADE_DIR_PARENT_PATH) + if(EXISTS ${OpenCASCADE_DIR_PARENT_PATH}/env.bat) + set(OpenCASCADE_DIR ${OpenCASCADE_DIR_PARENT_PATH}) + endif() +endif() + +# Generate file "OpenCascadeWin_env.cmake" +if(EXISTS "${OpenCASCADE_DIR}/env.bat") + execute_process( + COMMAND cmd /c ${PROJECT_SOURCE_DIR}/scripts/gen-cmake-opencascade-vars.bat "${OpenCASCADE_DIR}" + OUTPUT_FILE ${CMAKE_BINARY_DIR}/OpenCascadeWin_env.cmake + ) + include(${CMAKE_BINARY_DIR}/OpenCascadeWin_env.cmake) +endif() + +# Set OpenCASCADE variables +if(OpenCASCADE_FOUND) + # Set variables for OpenCASCADE version + file(READ ${OpenCASCADE_INCLUDE_DIR}/Standard_Version.hxx OpenCascade_FileVersionHxx) + string(REGEX MATCH "define[ \t]+OCC_VERSION_MAJOR[ \t]+([0-9]+)" OpenCASCADE_MAJOR_VERSION "${OpenCascade_FileVersionHxx}") + set(OpenCASCADE_MAJOR_VERSION ${CMAKE_MATCH_1}) + string(REGEX MATCH "define[ \t]+OCC_VERSION_MINOR[ \t]+([0-9]+)" OpenCASCADE_MINOR_VERSION "${OpenCascade_FileVersionHxx}") + set(OpenCASCADE_MINOR_VERSION ${CMAKE_MATCH_1}) + string(REGEX MATCH "define[ \t]+OCC_VERSION_MAINTENANCE[ \t]+([0-9]+)" OpenCASCADE_MAINTENANCE_VERSION "${OpenCascade_FileVersionHxx}") + set(OpenCASCADE_MAINTENANCE_VERSION ${CMAKE_MATCH_1}) + set(OpenCASCADE_VERSION "${OpenCASCADE_MAJOR_VERSION}.${OpenCASCADE_MINOR_VERSION}.${OpenCASCADE_MAINTENANCE_VERSION}") + + # Add OpenCASCADE "HAVE_RAPIDJSON" compile flags + if(OpenCASCADE_HAVE_RAPIDJSON STREQUAL "true") + list(APPEND Mayo_CompileDefinitions HAVE_RAPIDJSON) + elseif(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) + file(GLOB OpenCASCADE_CompileDefsCMakeFiles ${OpenCASCADE_DIR}/cmake/OpenCASCADECompileDefinitions*.cmake) + foreach(CMakeFile ${OpenCASCADE_CompileDefsCMakeFiles}) + file(READ "${CMakeFile}" CMakeFileContents) + if(CMakeFileContents MATCHES ":HAVE_RAPIDJSON>") + list(APPEND Mayo_CompileDefinitions HAVE_RAPIDJSON) + break() + endif() + endforeach() + endif() + + # Set OpenCASCADE_3RDPARTY_BINARY_DIRS variable that will contain the OpenCASCADE + # 3rd-party binary directories(ie containing DLL files) + set(OpenCASCADE_3RDPARTY_BINARY_DIRS) + if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) + list( + APPEND OpenCASCADE_3RDPARTY_BINARY_DIRS + ${TCL_DIR} + ${TK_DIR} + ${FREETYPE_DIR} + ${FREEIMAGE_DIR} + ${FFMPEG_DIR} + ${TBB_DIR} + ${OPENVR_DIR} + ${JEMALLOC_DIR} + ) + else() + if(Mayo_TargetArchBitSize STREQUAL "32") + list(APPEND OpenCASCADE_3RDPARTY_BINARY_DIRS ${CSF_OPT_BIN32}) + else() + list(APPEND OpenCASCADE_3RDPARTY_BINARY_DIRS ${CSF_OPT_BIN64}) + endif() + + # Remove Qt binary path probably added in CSF_OPT_BIN(32/64) variable + set(OpenCASCADE_QT_BINDIR) + foreach(BINDIR ${OpenCASCADE_3RDPARTY_BINARY_DIRS}) + if(BINDIR MATCHES "qt5") + set(OpenCASCADE_QT_BINDIR ${BINDIR}) + endif() + endforeach() + list(REMOVE_ITEM OpenCASCADE_3RDPARTY_BINARY_DIRS ${OpenCASCADE_QT_BINDIR}) + endif() + + # Create imported target for each toolkit + set( + OpenCASCADE_TOOLKITS + # FoundationClasses + TKernel TKMath + # ModelingData + TKG2d TKG3d TKGeomBase TKBRep + # ModelingAlgorithms + TKBO TKBool TKGeomAlgo TKHLR TKMesh TKPrim TKShHealing TKTopAlgo + # Visualization + TKMeshVS TKOpenGl TKService TKV3d + # ApplicationFramework + TKBin TKBinL TKBinXCAF TKCAF TKCDF TKLCAF TKVCAF TKXml TKXmlL + # DataExchange + TKXCAF TKXmlXCAF TKXSBase + ) + + if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) + list(APPEND OpenCASCADE_TOOLKITS TKDE TKDEIGES TKDESTEP TKDESTL TKDEVRML) + else() + list(APPEND OpenCASCADE_TOOLKITS TKIGES TKXDEIGES) + list(APPEND OpenCASCADE_TOOLKITS TKSTEP TKSTEP209 TKSTEPAttr TKSTEPBase TKXDESTEP) + list(APPEND OpenCASCADE_TOOLKITS TKSTL) + list(APPEND OpenCASCADE_TOOLKITS TKVRML) + if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.7.0) + list(APPEND OpenCASCADE_TOOLKITS TKXDE) + endif() + endif() + + if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.4.0) + list(APPEND OpenCASCADE_TOOLKITS TKRWMesh) + if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) + list(APPEND OpenCASCADE_TOOLKITS TKDEOBJ TKDEGLTF) + endif() + endif() + + foreach(OccTK ${OpenCASCADE_TOOLKITS}) + add_library(${OccTK} SHARED IMPORTED) + set_target_properties( + ${OccTK} PROPERTIES + IMPORTED_IMPLIB "${OpenCASCADE_LIBRARY_DIR}/${OccTK}.lib" + IMPORTED_LOCATION "${OpenCASCADE_BINARY_DIR}/${OccTK}.dll" + ) + endforeach() + + # Add OpenCASCADE library directories + # Note: QtCreator option "Run->Add build library search to PATH" will add to PATH env variable + # the contents of Mayo_LinkDirectories variable. For convenience, let's add also the + # directories containing DLLs + list( + APPEND Mayo_LinkDirectories + ${OpenCASCADE_BINARY_DIR} + ${OpenCASCADE_LIBRARY_DIR} + ${OpenCASCADE_3RDPARTY_BINARY_DIRS} + ) + + # List all 3rd-party DLLs required by OpenCASCADE + set(OpenCASCADE_3RDPARTY_DLLS) + foreach(OccBINDIR ${OpenCASCADE_3RDPARTY_BINARY_DIRS}) + file(GLOB OccBINDIR_DLLS ${OccBINDIR}/*.dll) + list(APPEND OpenCASCADE_3RDPARTY_DLLS ${OccBINDIR_DLLS}) + endforeach() +else() + message(WARNING "OpenCASCADE not found from this location OpenCASCADE_DIR='${OpenCASCADE_DIR}' ") +endif() From d598c955d5f33aeb85adeb310bb41b0f83c729b6 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 29 Feb 2024 17:59:43 +0100 Subject: [PATCH 027/101] Doc: add screenshots for CMake build on Windows --- doc/win_cmake_01.png | Bin 0 -> 21531 bytes doc/win_cmake_02.png | Bin 0 -> 9290 bytes doc/win_cmake_03.png | Bin 0 -> 18912 bytes doc/win_cmake_04.png | Bin 0 -> 14387 bytes doc/win_cmake_05.png | Bin 0 -> 21739 bytes doc/win_cmake_06.png | Bin 0 -> 17063 bytes doc/win_msvc_mp.png | Bin 0 -> 33309 bytes 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/win_cmake_01.png create mode 100644 doc/win_cmake_02.png create mode 100644 doc/win_cmake_03.png create mode 100644 doc/win_cmake_04.png create mode 100644 doc/win_cmake_05.png create mode 100644 doc/win_cmake_06.png create mode 100644 doc/win_msvc_mp.png diff --git a/doc/win_cmake_01.png b/doc/win_cmake_01.png new file mode 100644 index 0000000000000000000000000000000000000000..c94072a61a9e694cecb82745e44d1d4cf97a1464 GIT binary patch literal 21531 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 iOOMJ_?pafb#2|0|o{z^5%D|-6r=F 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%0Ap)-+`=JN(Tx{Fc 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 VV@j;aZVRBT44$rjF6*2UngGB>)C2$k literal 0 HcmV?d00001 diff --git a/doc/win_cmake_04.png b/doc/win_cmake_04.png new file mode 100644 index 0000000000000000000000000000000000000000..f2a6f7211391c3e074a82ed02adde7262465ff93 GIT binary patch literal 14387 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 G@BagZ7e%W8 literal 0 HcmV?d00001 diff --git a/doc/win_cmake_05.png b/doc/win_cmake_05.png new file mode 100644 index 0000000000000000000000000000000000000000..f7b8ac67b0b336ad1f87d4c0cab16b93d1ba5108 GIT binary patch literal 21739 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 literal 0 HcmV?d00001 diff --git a/doc/win_cmake_06.png b/doc/win_cmake_06.png new file mode 100644 index 0000000000000000000000000000000000000000..9a70ae380aa20114d62237b3e6559bbf4a47b4e8 GIT binary patch literal 17063 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

nmzdw*SYnNRO)wfDj92{XD}1~gR%h>mkotVE*m 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 W{U{=9vVnomw~C9%2p0?LdjCIhCA4q= literal 0 HcmV?d00001 diff --git a/doc/win_msvc_mp.png b/doc/win_msvc_mp.png new file mode 100644 index 0000000000000000000000000000000000000000..be4615f15f3414f2816dfeaaa2d3d408b72258c0 GIT binary patch literal 33309 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 literal 0 HcmV?d00001 From 81cc9d2475f2ecd7a5afa5a9cf03d7c1dfafc6d4 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 29 Feb 2024 18:47:03 +0100 Subject: [PATCH 028/101] README: update "build instructions" links[skip ci] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b130bf1..0e29e805 100644 --- a/README.md +++ b/README.md @@ -90,8 +90,8 @@ Select Objects | SHIFT + mouseLeft clicks Mayo supports also multiple 3D viewer navigation styles to mimic common CAD applications(CATIA, SOLIDWORKS, ...) ## :hammer: How to build Mayo -[Instructions for Windows MSVC](https://github.com/fougue/mayo/wiki/Build-instructions-for-Windows-MSVC) -[Instructions for Debian](https://github.com/fougue/mayo/wiki/Build-instructions-for-Debian) +[Instructions for Windows](https://github.com/fougue/mayo/wiki/Build-instructions-for-Windows) +[Instructions for Linux](https://github.com/fougue/mayo/wiki/Build-instructions-for-Linux) [Instructions for macOS](https://github.com/fougue/mayo/wiki/Build-instructions-for-macOS) ## :clapper: Gallery From c502b778a5cd76ee98e0f18229ff5bb0af6b1be9 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 29 Feb 2024 18:57:27 +0100 Subject: [PATCH 029/101] Doc: more explicit screenshot for MSVC build[skip ci] --- doc/win_msvc_mp.png | Bin 33309 -> 33630 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/win_msvc_mp.png b/doc/win_msvc_mp.png index be4615f15f3414f2816dfeaaa2d3d408b72258c0..e12b02f1501190024de4eec617261b160c31fa62 100644 GIT binary patch literal 33630 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 From ed7504d21de968bca5bf485db377d09a8bb931ae Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Tue, 5 Mar 2024 18:46:54 +0100 Subject: [PATCH 030/101] IO_OFF: fix case where vertex/face count is just after OFF marker Relates to GitHub #258 --- src/io_off/io_off_reader.cpp | 25 ++++++++++++++++-------- tests/inputs/#258_cube.off | 37 ++++++++++++++++++++++++++++++++++++ tests/test_base.cpp | 33 ++++++++++++++++++++++++++++++-- tests/test_base.h | 1 + 4 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 tests/inputs/#258_cube.off diff --git a/src/io_off/io_off_reader.cpp b/src/io_off/io_off_reader.cpp index 2c87f7ee..e3572b84 100644 --- a/src/io_off/io_off_reader.cpp +++ b/src/io_off/io_off_reader.cpp @@ -200,16 +200,25 @@ bool OffReader::readFile(const FilePath& filepath, TaskProgress* progress) int vertexCount = 0; int facetCount = 0; { - if (!ifs.good()) - return fnError(OffReaderI18N::textIdTr("Unexpected end of file")); + // Normally vertex/face/edge counts are specified on a dedicated line coming after OFF + // But for some files they are wrongly specified on the line containing OFF token, eg "OFF 24 12 0" + const auto arrayStrFirstLine = getWords<3>(strLine); + if (!arrayStrFirstLine[1].empty() && !arrayStrFirstLine[2].empty()) { + vertexCount = strToNum(arrayStrFirstLine[1]); + facetCount = strToNum(arrayStrFirstLine[2]); + } + else { + if (!ifs.good()) + return fnError(OffReaderI18N::textIdTr("Unexpected end of file")); - getNonCommentLine(ifs, strLine); - const auto arrayStrCount = getWords<2>(strLine); - if (hasEmptyString(arrayStrCount)) - return fnError(OffReaderI18N::textIdTr("No vertex or face count")); + getNonCommentLine(ifs, strLine); + const auto arrayStrCount = getWords<2>(strLine); + if (hasEmptyString(arrayStrCount)) + return fnError(OffReaderI18N::textIdTr("No vertex or face count")); - vertexCount = strToNum(arrayStrCount[0]); - facetCount = strToNum(arrayStrCount[1]); + vertexCount = strToNum(arrayStrCount[0]); + facetCount = strToNum(arrayStrCount[1]); + } } // Helper function for progress report diff --git a/tests/inputs/#258_cube.off b/tests/inputs/#258_cube.off new file mode 100644 index 00000000..c71df4a9 --- /dev/null +++ b/tests/inputs/#258_cube.off @@ -0,0 +1,37 @@ +OFF 24 12 0 +0 0 0 0.603827 0.603827 0.603827 +0 0 10 0.603827 0.603827 0.603827 +0 10 0 0.603827 0.603827 0.603827 +0 10 10 0.603827 0.603827 0.603827 +10 0 0 0.603827 0.603827 0.603827 +10 0 10 0.603827 0.603827 0.603827 +10 10 0 0.603827 0.603827 0.603827 +10 10 10 0.603827 0.603827 0.603827 +0 0 0 0.603827 0.603827 0.603827 +10 0 0 0.603827 0.603827 0.603827 +0 0 10 0.603827 0.603827 0.603827 +10 0 10 0.603827 0.603827 0.603827 +0 10 0 0.603827 0.603827 0.603827 +10 10 0 0.603827 0.603827 0.603827 +0 10 10 0.603827 0.603827 0.603827 +10 10 10 0.603827 0.603827 0.603827 +0 0 0 0.603827 0.603827 0.603827 +0 10 0 0.603827 0.603827 0.603827 +10 0 0 0.603827 0.603827 0.603827 +10 10 0 0.603827 0.603827 0.603827 +0 0 10 0.603827 0.603827 0.603827 +0 10 10 0.603827 0.603827 0.603827 +10 0 10 0.603827 0.603827 0.603827 +10 10 10 0.603827 0.603827 0.603827 +3 1 0 2 +3 1 2 3 +3 5 4 6 +3 5 6 7 +3 11 9 8 +3 11 8 10 +3 15 13 12 +3 15 12 14 +3 19 17 16 +3 19 16 18 +3 23 21 20 +3 23 20 22 diff --git a/tests/test_base.cpp b/tests/test_base.cpp index 2be649ad..74cfdef7 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -35,6 +35,8 @@ #include "../src/base/unit_system.h" #include "../src/io_dxf/io_dxf.h" #include "../src/io_occ/io_occ.h" +#include "../src/io_off/io_off_reader.h" +#include "../src/io_off/io_off_writer.h" #include "../src/io_ply/io_ply_reader.h" #include "../src/io_ply/io_ply_writer.h" @@ -622,6 +624,28 @@ void TestBase::IO_bugGitHub166_test_data() #endif } +void TestBase::IO_bugGitHub258_test() +{ + auto app = Application::instance(); + DocumentPtr doc = app->newDocument(); + const bool okImport = m_ioSystem->importInDocument() + .targetDocument(doc) + .withFilepath("tests/inputs/#258_cube.off") + .execute(); + QVERIFY(okImport); + QVERIFY(doc->entityCount() == 1); + + const TopoDS_Shape shape = doc->xcaf().shape(doc->entityLabel(0)); + const TopoDS_Face& face = TopoDS::Face(shape); + TopLoc_Location locFace; + auto triangulation = BRep_Tool::Triangulation(face, locFace); + QVERIFY(!triangulation.IsNull()); + QCOMPARE(triangulation->NbNodes(), 24); + QCOMPARE(triangulation->NbTriangles(), 12); + + app->closeDocument(doc); +} + void TestBase::DoubleToString_test() { const std::locale frLocale = getFrLocale(); @@ -1102,11 +1126,16 @@ void TestBase::Span_test() void TestBase::initTestCase() { m_ioSystem = new IO::System; + m_ioSystem->addFactoryReader(std::make_unique()); - m_ioSystem->addFactoryReader(std::make_unique()); - m_ioSystem->addFactoryWriter(std::make_unique()); m_ioSystem->addFactoryReader(std::make_unique()); + m_ioSystem->addFactoryReader(std::make_unique()); + m_ioSystem->addFactoryReader(std::make_unique()); + m_ioSystem->addFactoryWriter(std::make_unique()); + m_ioSystem->addFactoryWriter(std::make_unique()); + m_ioSystem->addFactoryWriter(std::make_unique()); + IO::addPredefinedFormatProbes(m_ioSystem); } diff --git a/tests/test_base.h b/tests/test_base.h index b5a73817..5723fe7e 100644 --- a/tests/test_base.h +++ b/tests/test_base.h @@ -43,6 +43,7 @@ private slots: void IO_OccStaticVariablesRollback_test_data(); void IO_bugGitHub166_test(); void IO_bugGitHub166_test_data(); + void IO_bugGitHub258_test(); void DoubleToString_test(); void StringConv_test(); From c9dd2066807b788367db2bbf61df25dec1fa45ba Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 7 Mar 2024 09:36:32 +0100 Subject: [PATCH 031/101] CMake: introduce mayo_config.h for global compile definitions --- CMakeLists.txt | 23 ++++++++++++------- src/app/command_system_information.cpp | 2 +- src/app/dialog_about.cpp | 3 ++- src/app/main.cpp | 2 +- src/io_assimp/io_assimp.cpp | 6 ++--- src/io_assimp/io_assimp.h | 6 +++-- src/io_gmio/io_gmio.h | 5 ++-- src/io_gmio/io_gmio_amf_writer.cpp | 6 ++--- src/mayo_config.h.cmake | 16 +++++++++++++ .../version.h.cmake => mayo_version.h.cmake} | 6 +++++ tests/test_base.cpp | 5 ++-- 11 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 src/mayo_config.h.cmake rename src/{app/version.h.cmake => mayo_version.h.cmake} (54%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f5011905..dfe0cee5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,8 +55,6 @@ if(Git_FOUND) endif() endif() -configure_file(${PROJECT_SOURCE_DIR}/src/app/version.h.cmake app/version.h @ONLY) - list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") # Find bit size of the target machine @@ -204,7 +202,7 @@ set( Mayo_IncludeDirectories src/app src/3rdparty - ${CMAKE_BINARY_DIR}/app + ${CMAKE_BINARY_DIR} ) ########## @@ -276,7 +274,7 @@ if(Mayo_BuildTests) list(APPEND Mayo_HeaderFiles ${MayoTests_HeaderFiles}) list(APPEND Mayo_SourceFiles ${MayoTests_SourceFiles}) - list(APPEND Mayo_CompileDefinitions MAYO_WITH_TESTS) + set(MAYO_WITH_TESTS 1) list(APPEND Mayo_LinkLibraries Qt${QT_VERSION_MAJOR}::Test) file(GLOB MayoTests_InputFiles ${PROJECT_SOURCE_DIR}/tests/inputs/*.*) @@ -426,12 +424,13 @@ if(assimp_FOUND) list(APPEND Mayo_SourceFiles ${MayoPluginAssimp_SourceFiles}) list(APPEND Mayo_LinkLibraries ${ASSIMP_LIBRARIES}) - list(APPEND Mayo_CompileDefinitions HAVE_ASSIMP) + set(MAYO_HAVE_ASSIMP 1) list(GET ASSIMP_INCLUDE_DIRS 0 assimp_IncludeDir) file(READ "${assimp_IncludeDir}/assimp/version.h" assimp_FileVersionH) - if(NOT assimp_FileVersionH MATCHES "aiGetVersionPatch") - list(APPEND Mayo_CompileDefinitions NO_ASSIMP_aiGetVersionPatch) + if(assimp_FileVersionH MATCHES "aiGetVersionPatch") + set(MAYO_HAVE_ASSIMP_aiGetVersionPatch 1) + else() message(STATUS "Assimp function aiGetVersionPatch() not available") endif() endif() @@ -443,6 +442,13 @@ endif() # TODO +########## +# Configure files +########## + +configure_file(${PROJECT_SOURCE_DIR}/src/mayo_config.h.cmake common/mayo_config.h @ONLY) +configure_file(${PROJECT_SOURCE_DIR}/src/mayo_version.h.cmake common/mayo_version.h @ONLY) + ########## # Targets ########## @@ -491,7 +497,8 @@ add_custom_target( images/credits.txt scripts/bump-version.rb scripts/gen-cmake-opencascade-vars.bat - src/app/version.h.cmake + src/mayo_config.h.cmake + src/mayo_version.h.cmake README.md ) diff --git a/src/app/command_system_information.cpp b/src/app/command_system_information.cpp index 5fb843f9..05017e43 100644 --- a/src/app/command_system_information.cpp +++ b/src/app/command_system_information.cpp @@ -10,10 +10,10 @@ #include "command_system_information_occopengl.h" #include "qstring_conv.h" #include "qtwidgets_utils.h" -#include "version.h" #include "../base/meta_enum.h" #include "../base/filepath.h" #include "../base/io_system.h" +#include #include #include diff --git a/src/app/dialog_about.cpp b/src/app/dialog_about.cpp index 86d674bb..d117a6ba 100644 --- a/src/app/dialog_about.cpp +++ b/src/app/dialog_about.cpp @@ -8,7 +8,8 @@ #include "ui_dialog_about.h" #include "qstring_conv.h" -#include "version.h" +#include + #include namespace Mayo { diff --git a/src/app/main.cpp b/src/app/main.cpp index d7eed7ab..ec9d611d 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -32,11 +32,11 @@ #include "qstring_conv.h" #include "qtgui_utils.h" #include "theme.h" -#include "version.h" #include "widget_model_tree.h" #include "widget_model_tree_builder_mesh.h" #include "widget_model_tree_builder_xde.h" #include "widget_occ_view.h" +#include #include #include diff --git a/src/io_assimp/io_assimp.cpp b/src/io_assimp/io_assimp.cpp index 98d74fc3..8c03831c 100644 --- a/src/io_assimp/io_assimp.cpp +++ b/src/io_assimp/io_assimp.cpp @@ -7,9 +7,7 @@ #include "io_assimp.h" #include "io_assimp_reader.h" -#ifdef HAVE_ASSIMP -# include -#endif +#include namespace Mayo { namespace IO { @@ -47,7 +45,7 @@ std::string_view AssimpLib::strVersion() if (str.empty()) { str += std::to_string(aiGetVersionMajor()) + "." + std::to_string(aiGetVersionMinor()) -#ifndef NO_ASSIMP_aiGetVersionPatch +#ifdef MAYO_HAVE_ASSIMP_aiGetVersionPatch + "." + std::to_string(aiGetVersionPatch()) #else + ".?" diff --git a/src/io_assimp/io_assimp.h b/src/io_assimp/io_assimp.h index d8a25b04..5e30d1c7 100644 --- a/src/io_assimp/io_assimp.h +++ b/src/io_assimp/io_assimp.h @@ -6,6 +6,8 @@ #include "../base/io_reader.h" #include "../base/property.h" +#include + #include namespace Mayo { @@ -19,7 +21,7 @@ class AssimpFactoryReader : public FactoryReader { std::unique_ptr createProperties(Format format, PropertyGroup* parentGroup) const override; static std::unique_ptr create() { -#ifdef HAVE_ASSIMP +#ifdef MAYO_HAVE_ASSIMP return std::make_unique(); #else return {}; @@ -29,7 +31,7 @@ class AssimpFactoryReader : public FactoryReader { struct AssimpLib { static std::string_view strName() { return "Assimp"; } -#ifdef HAVE_ASSIMP +#ifdef MAYO_HAVE_ASSIMP static std::string_view strVersion(); static std::string_view strVersionDetails(); #else diff --git a/src/io_gmio/io_gmio.h b/src/io_gmio/io_gmio.h index 5ec737c0..f6c42cdf 100644 --- a/src/io_gmio/io_gmio.h +++ b/src/io_gmio/io_gmio.h @@ -8,6 +8,7 @@ #include "../base/io_writer.h" #include "../base/property.h" +#include #include @@ -22,7 +23,7 @@ class GmioFactoryWriter : public FactoryWriter { std::unique_ptr createProperties(Format format, PropertyGroup* parentGroup) const override; static std::unique_ptr create() { -#ifdef HAVE_GMIO +#ifdef MAYO_HAVE_GMIO return std::make_unique(); #else return {}; @@ -32,7 +33,7 @@ class GmioFactoryWriter : public FactoryWriter { struct GmioLib { static std::string_view strName() { return "gmio"; } -#ifdef HAVE_GMIO +#ifdef MAYO_HAVE_GMIO static std::string_view strVersion(); static std::string_view strVersionDetails() { return "(build)"; } #else diff --git a/src/io_gmio/io_gmio_amf_writer.cpp b/src/io_gmio/io_gmio_amf_writer.cpp index 8c2d0199..4222c816 100644 --- a/src/io_gmio/io_gmio_amf_writer.cpp +++ b/src/io_gmio/io_gmio_amf_writer.cpp @@ -64,13 +64,13 @@ gmio_task_iface gmio_createTask(TaskProgress* progress) return task; } -//#ifdef HAVE_GMIO +//#ifdef MAYO_HAVE_GMIO #if 0 Format System::probeFormat(const QString& filepath) const { QFile file(filepath); if (file.open(QIODevice::ReadOnly)) { -//#ifdef HAVE_GMIO +//#ifdef MAYO_HAVE_GMIO gmio_stream qtstream = gmio_stream_qiodevice(&file); const gmio_stl_format stlFormat = gmio_stl_format_probe(&qtstream); if (stlFormat != GMIO_STL_FORMAT_UNKNOWN) @@ -119,7 +119,7 @@ IO::Result IO::exportStl_gmio(ExportData data) return Result::error(file.errorString()); } -#endif // HAVE_GMIO +#endif // MAYO_HAVE_GMIO } // namespace diff --git a/src/mayo_config.h.cmake b/src/mayo_config.h.cmake new file mode 100644 index 00000000..57eab56c --- /dev/null +++ b/src/mayo_config.h.cmake @@ -0,0 +1,16 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#cmakedefine MAYO_WITH_TESTS +#cmakedefine MAYO_HAVE_ASSIMP +#cmakedefine MAYO_HAVE_ASSIMP_aiGetVersionPatch +#cmakedefine MAYO_HAVE_GMIO + +#ifdef HAVE_RAPIDJSON +# define OPENCASCADE_HAVE_RAPIDJSON +#endif diff --git a/src/app/version.h.cmake b/src/mayo_version.h.cmake similarity index 54% rename from src/app/version.h.cmake rename to src/mayo_version.h.cmake index 772191de..f4bb90e8 100644 --- a/src/app/version.h.cmake +++ b/src/mayo_version.h.cmake @@ -1,3 +1,9 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + #pragma once namespace Mayo { diff --git a/tests/test_base.cpp b/tests/test_base.cpp index 74cfdef7..ee3baccf 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -39,6 +39,7 @@ #include "../src/io_off/io_off_writer.h" #include "../src/io_ply/io_ply_reader.h" #include "../src/io_ply/io_ply_writer.h" +#include #include #include @@ -608,7 +609,7 @@ void TestBase::IO_bugGitHub166_test_data() #if OCC_VERSION_HEX >= 0x070400 QTest::newRow("OBJ->PLY") << "tests/inputs/cube.obj" << "tests/outputs/cube.ply" << IO::Format_PLY; QTest::newRow("OBJ->STL") << "tests/inputs/cube.obj" << "tests/outputs/cube.stl" << IO::Format_STL; -# ifdef HAVE_RAPIDJSON +# ifdef OPENCASCADE_HAVE_RAPIDJSON QTest::newRow("glTF->PLY") << "tests/inputs/cube.gltf" << "tests/outputs/cube.ply" << IO::Format_PLY; QTest::newRow("glTF->STL") << "tests/inputs/cube.gltf" << "tests/outputs/cube.stl" << IO::Format_STL; # endif @@ -617,7 +618,7 @@ void TestBase::IO_bugGitHub166_test_data() #if OCC_VERSION_HEX >= 0x070600 QTest::newRow("PLY->OBJ") << "tests/inputs/cube.ply" << "tests/outputs/cube.obj" << IO::Format_OBJ; QTest::newRow("STL->OBJ") << "tests/inputs/cube.stla" << "tests/outputs/cube.obj" << IO::Format_OBJ; -# ifdef HAVE_RAPIDJSON +# ifdef OPENCASCADE_HAVE_RAPIDJSON QTest::newRow("glTF->OBJ") << "tests/inputs/cube.gltf" << "tests/outputs/cube.obj" << IO::Format_OBJ; QTest::newRow("OBJ->glTF") << "tests/inputs/cube.obj" << "tests/outputs/cube.glTF" << IO::Format_GLTF; # endif From b474196a00a6780933af0d0c17aa01f8aa363b6a Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 7 Mar 2024 09:59:41 +0100 Subject: [PATCH 032/101] CMake: skeleton for gmio handling --- CMakeLists.txt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dfe0cee5..4cb34bc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -243,7 +243,6 @@ file( ${PROJECT_SOURCE_DIR}/src/graphics/*.h ${PROJECT_SOURCE_DIR}/src/gui/*.h ${PROJECT_SOURCE_DIR}/src/io_dxf/*.h - ${PROJECT_SOURCE_DIR}/src/io_gmio/*.h ${PROJECT_SOURCE_DIR}/src/io_image/*.h ${PROJECT_SOURCE_DIR}/src/io_occ/*.h ${PROJECT_SOURCE_DIR}/src/io_off/*.h @@ -417,6 +416,7 @@ endif() if(assimp_FOUND) message(STATUS "assimp version ${assimp_VERSION}") + set(MAYO_HAVE_ASSIMP 1) file(GLOB MayoPluginAssimp_HeaderFiles ${PROJECT_SOURCE_DIR}/src/io_assimp/*.h) file(GLOB MayoPluginAssimp_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_assimp/*.cpp) @@ -424,7 +424,6 @@ if(assimp_FOUND) list(APPEND Mayo_SourceFiles ${MayoPluginAssimp_SourceFiles}) list(APPEND Mayo_LinkLibraries ${ASSIMP_LIBRARIES}) - set(MAYO_HAVE_ASSIMP 1) list(GET ASSIMP_INCLUDE_DIRS 0 assimp_IncludeDir) file(READ "${assimp_IncludeDir}/assimp/version.h" assimp_FileVersionH) @@ -439,8 +438,19 @@ endif() # gmio ########## -# TODO +# TODO Finalize +if(gmio_FOUND) + message(STATUS "gmio version ${gmio_VERSION}") + set(MAYO_HAVE_GMIO 1) + file(GLOB MayoPluginGmio_HeaderFiles ${PROJECT_SOURCE_DIR}/src/io_gmio/*.h) + file(GLOB MayoPluginGmio_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_gmio/*.cpp) + list(APPEND Mayo_HeaderFiles ${MayoPluginGmio_HeaderFiles}) + list(APPEND Mayo_SourceFiles ${MayoPluginGmio_SourceFiles}) + + # Needs -L$$GMIO_ROOT/lib -lgmio_static -lzlibstatic + list(APPEND Mayo_LinkLibraries ${GMIO_LIBRARIES}) +endif() ########## # Configure files From 66c7c548a08bee024a5c16c73d231e747afff1ee Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 7 Mar 2024 10:01:56 +0100 Subject: [PATCH 033/101] QMake: move tests/tests.pri into scripts/qmake --- {tests => scripts/qmake}/tests.pri | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {tests => scripts/qmake}/tests.pri (100%) diff --git a/tests/tests.pri b/scripts/qmake/tests.pri similarity index 100% rename from tests/tests.pri rename to scripts/qmake/tests.pri From 3f66f59a21554271435c09259f9fa0c49fe3a4fc Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 11 Mar 2024 17:15:21 +0100 Subject: [PATCH 034/101] README: add "Install" section [skip ci] --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e29e805..ef9ff89c 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,21 @@ Select Objects | SHIFT + mouseLeft clicks Mayo supports also multiple 3D viewer navigation styles to mimic common CAD applications(CATIA, SOLIDWORKS, ...) -## :hammer: How to build Mayo +## :package: Install +Release packages are available for Windows and Linux on the [Releases](https://github.com/fougue/mayo/releases) page. + +Alternatives for Windows: +- [Winget](https://winstall.app/apps/Fougue.Mayo) +```` +winget install --id Fougue.Mayo +```` +- [Scoop](https://scoop.sh/#/apps?s=0&d=1&o=true&q=mayo) +```` +scoop bucket add extras +scoop install extras/mayo +```` + +## :hammer: How to build [Instructions for Windows](https://github.com/fougue/mayo/wiki/Build-instructions-for-Windows) [Instructions for Linux](https://github.com/fougue/mayo/wiki/Build-instructions-for-Linux) [Instructions for macOS](https://github.com/fougue/mayo/wiki/Build-instructions-for-macOS) From 8e3df4dc99a51d2772341d7b891d8a05bdf36936 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 14 Mar 2024 16:07:58 +0100 Subject: [PATCH 035/101] Make Mayo GUI and CLI distinct applications Relates to GitHub #260 --- CMakeLists.txt | 442 +++++++++++------- cmake/OpenCascadeWin.cmake | 11 - src/app/app_module.cpp | 168 +++++-- src/app/app_module.h | 9 + src/app/app_module_properties.cpp | 2 +- src/app/app_module_properties.h | 4 +- src/app/command_system_information.cpp | 2 +- src/app/commands_display.cpp | 2 +- src/app/commands_file.cpp | 4 +- src/app/commands_help.cpp | 2 +- src/app/dialog_about.cpp | 2 +- src/app/dialog_inspect_xde.cpp | 4 +- src/app/dialog_options.cpp | 7 +- src/app/dialog_task_manager.cpp | 2 +- src/app/document_property_group.cpp | 4 +- ...ocument_tree_node_properties_providers.cpp | 2 +- src/app/gui_document_list_model.cpp | 4 +- src/app/main.cpp | 231 ++------- src/app/property_editor_factory.cpp | 4 +- src/app/property_item_delegate.cpp | 4 +- src/app/qtgui_utils.cpp | 66 ++- src/app/qtgui_utils.h | 7 + src/app/recent_files.cpp | 106 +---- src/app/recent_files.h | 24 +- src/app/view3d_navigation_style.h | 15 + src/app/widget_gui_document.cpp | 55 +-- src/app/widget_home_files.cpp | 14 +- src/app/widget_main_control.cpp | 4 +- src/app/widget_main_home.cpp | 2 +- src/app/widget_measure.cpp | 2 +- src/app/widget_model_tree.cpp | 2 +- src/app/widget_model_tree_builder.cpp | 5 +- src/app/widget_model_tree_builder_xde.cpp | 4 +- src/app/widget_occ_view_controller.cpp | 16 +- src/app/widget_occ_view_controller.h | 12 +- src/app/widget_occ_view_impl.cpp | 26 +- src/app/widget_properties_editor.cpp | 2 +- src/{app => cli}/cli_export.cpp | 4 +- src/{app => cli}/cli_export.h | 0 src/{app => cli}/console.cpp | 45 +- src/{app => cli}/console.h | 7 - src/cli/main.cpp | 351 ++++++++++++++ src/graphics/graphics_utils.cpp | 11 + src/graphics/graphics_utils.h | 2 + src/{app => qtbackend}/qsettings_storage.cpp | 4 +- src/{app => qtbackend}/qsettings_storage.h | 0 src/qtbackend/qt_animation_backend.cpp | 58 +++ src/qtbackend/qt_animation_backend.h | 48 ++ src/qtbackend/qt_app_translator.cpp | 37 ++ src/qtbackend/qt_app_translator.h | 18 + src/qtbackend/qt_signal_thread_helper.cpp | 26 ++ src/qtbackend/qt_signal_thread_helper.h | 21 + src/{app => qtcommon}/filepath_conv.h | 0 src/qtcommon/log_message_handler.cpp | 70 +++ src/qtcommon/log_message_handler.h | 38 ++ src/qtcommon/qstring_conv.cpp | 41 ++ src/{app => qtcommon}/qstring_conv.h | 4 + src/{app => qtcommon}/qtcore_hfuncs.h | 0 src/{app => qtcommon}/qtcore_utils.h | 0 tests/test_app.cpp | 95 ++-- tests/test_app.h | 1 + 61 files changed, 1416 insertions(+), 737 deletions(-) create mode 100644 src/app/view3d_navigation_style.h rename src/{app => cli}/cli_export.cpp (99%) rename src/{app => cli}/cli_export.h (100%) rename src/{app => cli}/console.cpp (83%) rename src/{app => cli}/console.h (82%) create mode 100644 src/cli/main.cpp rename src/{app => qtbackend}/qsettings_storage.cpp (97%) rename src/{app => qtbackend}/qsettings_storage.h (100%) create mode 100644 src/qtbackend/qt_animation_backend.cpp create mode 100644 src/qtbackend/qt_animation_backend.h create mode 100644 src/qtbackend/qt_app_translator.cpp create mode 100644 src/qtbackend/qt_app_translator.h create mode 100644 src/qtbackend/qt_signal_thread_helper.cpp create mode 100644 src/qtbackend/qt_signal_thread_helper.h rename src/{app => qtcommon}/filepath_conv.h (100%) create mode 100644 src/qtcommon/log_message_handler.cpp create mode 100644 src/qtcommon/log_message_handler.h create mode 100644 src/qtcommon/qstring_conv.cpp rename src/{app => qtcommon}/qstring_conv.h (96%) rename src/{app => qtcommon}/qtcore_hfuncs.h (100%) rename src/{app => qtcommon}/qtcore_utils.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cb34bc5..90b48c26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,9 +81,12 @@ This simplifies running Mayo from eg Visual Studio IDE\ " ${Mayo_PostBuildCopyRuntimeDLLs_DefaultValue} ) - endif() +# TODO Make option Mayo_BuildTests dependent of Mayo_BuildApp) +option(Mayo_BuildApp "Build Mayo GUI application" ON) +option(Mayo_BuildConvCli "Build Mayo CLI converter" ON) + # TODO # option(Mayo_BuildPluginGmio "Build plugin to import/export mesh files supported by gmio" OFF) @@ -93,21 +96,28 @@ endif() find_package(QT NAMES Qt6 Qt5 REQUIRED) if(QT_FOUND) - set("Qt${QT_VERSION_MAJOR}_DIR" "${QT_DIR}") - find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widgets Test) - if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 REQUIRED COMPONENTS WinExtras) - elseif(QT_VERSION_MAJOR EQUAL 6) - find_package(Qt6 REQUIRED COMPONENTS OpenGLWidgets) + message(STATUS "Qt version ${QT_VERSION}") + if(QT_VERSION VERSION_LESS 5.14) + message(FATAL_ERROR "Qt >= 5.14 is required but detected version is ${QT_VERSION}") endif() - if(Mayo_PostBuildCopyRuntimeDLLs) - find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Svg) + set("Qt${QT_VERSION_MAJOR}_DIR" "${QT_DIR}") + + if(Mayo_BuildApp OR Mayo_BuildConvCli) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) endif() - message(STATUS "Qt version ${QT_VERSION}") - if(QT_VERSION VERSION_LESS 5.14) - message(FATAL_ERROR "Qt >= 5.14 is required but detected version is ${QT_VERSION}") + if(Mayo_BuildApp) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Widgets Test) + if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) + find_package(Qt5 REQUIRED COMPONENTS WinExtras) + elseif(QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 REQUIRED COMPONENTS OpenGLWidgets) + endif() + + if(Mayo_PostBuildCopyRuntimeDLLs) + find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Svg) + endif() endif() endif() @@ -168,30 +178,14 @@ endif() # Linker config ########## -set(Mayo_LinkLibraries) -set(Mayo_LinkDirectories) - -list( - APPEND Mayo_LinkLibraries - Qt${QT_VERSION_MAJOR}::Core - Qt${QT_VERSION_MAJOR}::Gui - Qt${QT_VERSION_MAJOR}::Widgets -) - -if(Mayo_PostBuildCopyRuntimeDLLs) - list(APPEND Mayo_LinkLibraries Qt${QT_VERSION_MAJOR}::Svg) -endif() - -if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) - list(APPEND Mayo_LinkLibraries Qt5::WinExtras) -elseif(QT_VERSION_MAJOR EQUAL 6) - list(APPEND Mayo_LinkLibraries Qt6::OpenGLWidgets) -endif() +set(MayoCore_LinkDirectories) +set(MayoCore_LinkLibraries) +set(MayoIO_LinkLibraries) if(MSVC) - list(APPEND Mayo_LinkLibraries Opengl32 User32) + list(APPEND MayoCore_LinkLibraries Opengl32 User32) elseif(APPLE) - list(APPEND Mayo_LinkLibraries iconv) + list(APPEND MayoCore_LinkLibraries iconv) endif() ########## @@ -200,67 +194,49 @@ endif() set( Mayo_IncludeDirectories - src/app src/3rdparty ${CMAKE_BINARY_DIR} ) ########## -# Source files +# MayoCore+MayoIO source files ########## file( - GLOB Mayo_SourceFiles - ${PROJECT_SOURCE_DIR}/src/app/*.cpp + GLOB MayoCore_SourceFiles ${PROJECT_SOURCE_DIR}/src/base/*.cpp ${PROJECT_SOURCE_DIR}/src/graphics/*.cpp ${PROJECT_SOURCE_DIR}/src/gui/*.cpp + ${PROJECT_SOURCE_DIR}/src/3rdparty/fmt/src/format.cc +) + +file( + GLOB MayoIO_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_dxf/*.cpp ${PROJECT_SOURCE_DIR}/src/io_image/*.cpp ${PROJECT_SOURCE_DIR}/src/io_occ/*.cpp ${PROJECT_SOURCE_DIR}/src/io_off/*.cpp ${PROJECT_SOURCE_DIR}/src/io_ply/*.cpp - ${PROJECT_SOURCE_DIR}/src/measure/*.cpp - ${PROJECT_SOURCE_DIR}/src/3rdparty/fmt/src/format.cc ) -if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) - list(APPEND Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.cpp) -endif() - -if(MSVC) - set(Mayo_RcIconsWin ${PROJECT_SOURCE_DIR}/images/appicon.rc) -endif() - ########## -# Header files +# MayoCore+MayoIO header files ########## file( - GLOB Mayo_HeaderFiles - ${PROJECT_SOURCE_DIR}/src/app/*.h + GLOB MayoCore_HeaderFiles ${PROJECT_SOURCE_DIR}/src/base/*.h ${PROJECT_SOURCE_DIR}/src/graphics/*.h ${PROJECT_SOURCE_DIR}/src/gui/*.h +) + +file( + GLOB MayoIO_HeaderFiles ${PROJECT_SOURCE_DIR}/src/io_dxf/*.h ${PROJECT_SOURCE_DIR}/src/io_image/*.h ${PROJECT_SOURCE_DIR}/src/io_occ/*.h ${PROJECT_SOURCE_DIR}/src/io_off/*.h ${PROJECT_SOURCE_DIR}/src/io_ply/*.h - ${PROJECT_SOURCE_DIR}/src/io_measure/*.h -) - -if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) - list(APPEND Mayo_HeaderFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.h) -endif() - -########## -# UI files -########## - -file( - GLOB Mayo_QtUiFiles - ${PROJECT_SOURCE_DIR}/src/app/*.ui ) ########## @@ -270,11 +246,9 @@ file( if(Mayo_BuildTests) file(GLOB MayoTests_HeaderFiles ${PROJECT_SOURCE_DIR}/tests/*.h) file(GLOB MayoTests_SourceFiles ${PROJECT_SOURCE_DIR}/tests/*.cpp) - list(APPEND Mayo_HeaderFiles ${MayoTests_HeaderFiles}) - list(APPEND Mayo_SourceFiles ${MayoTests_SourceFiles}) set(MAYO_WITH_TESTS 1) - list(APPEND Mayo_LinkLibraries Qt${QT_VERSION_MAJOR}::Test) + list(APPEND MayoTests_LinkLibraries Qt${QT_VERSION_MAJOR}::Test) file(GLOB MayoTests_InputFiles ${PROJECT_SOURCE_DIR}/tests/inputs/*.*) file(COPY ${MayoTests_InputFiles} DESTINATION ${CMAKE_BINARY_DIR}/tests/inputs) @@ -295,14 +269,14 @@ if(WIN32) # Add OpenCASCADE library directories # Note: QtCreator option "Run->Add build library search to PATH" will add to PATH env variable - # the contents of Mayo_LinkDirectories variable. For convenience, let's add also the + # the contents of MayoCore_LinkDirectories variable. For convenience, let's add also the # directories containing DLLs list( - APPEND Mayo_LinkDirectories + APPEND MayoCore_LinkDirectories ${OpenCASCADE_BINARY_DIR} ${OpenCASCADE_LIBRARY_DIR} ${OpenCASCADE_3RDPARTY_BINARY_DIRS} - ) + ) endif() else() find_package(OpenCASCADE REQUIRED) @@ -334,7 +308,7 @@ if(OpenCASCADE_FOUND) # Add OpenCASCADE libraries list( - APPEND Mayo_LinkLibraries + APPEND MayoCore_LinkLibraries # FoundationClasses TKernel TKMath # ModelingData @@ -350,30 +324,30 @@ if(OpenCASCADE_FOUND) ) if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) - list(APPEND Mayo_LinkLibraries TKDE) - list(APPEND Mayo_LinkLibraries TKDEIGES) - list(APPEND Mayo_LinkLibraries TKDESTEP) - list(APPEND Mayo_LinkLibraries TKDESTL) - list(APPEND Mayo_LinkLibraries TKDEVRML) + list(APPEND MayoIO_LinkLibraries TKDE) + list(APPEND MayoIO_LinkLibraries TKDEIGES) + list(APPEND MayoIO_LinkLibraries TKDESTEP) + list(APPEND MayoIO_LinkLibraries TKDESTL) + list(APPEND MayoIO_LinkLibraries TKDEVRML) else() - list(APPEND Mayo_LinkLibraries TKIGES TKXDEIGES) - list(APPEND Mayo_LinkLibraries TKSTEP TKSTEP209 TKSTEPAttr TKSTEPBase TKXDESTEP) - list(APPEND Mayo_LinkLibraries TKSTL) - list(APPEND Mayo_LinkLibraries TKVRML) + list(APPEND MayoIO_LinkLibraries TKIGES TKXDEIGES) + list(APPEND MayoIO_LinkLibraries TKSTEP TKSTEP209 TKSTEPAttr TKSTEPBase TKXDESTEP) + list(APPEND MayoIO_LinkLibraries TKSTL) + list(APPEND MayoIO_LinkLibraries TKVRML) if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.7.0) - list(APPEND Mayo_LinkLibraries TKXDE) + list(APPEND MayoIO_LinkLibraries TKXDE) endif() endif() # OBJ/glTF support if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.4.0) - list(APPEND Mayo_LinkLibraries TKRWMesh) + list(APPEND MayoIO_LinkLibraries TKRWMesh) if(OpenCASCADE_VERSION VERSION_GREATER_EQUAL 7.8.0) - list(APPEND Mayo_LinkLibraries TKDEOBJ TKDEGLTF) + list(APPEND MayoIO_LinkLibraries TKDEOBJ TKDEGLTF) endif() else() list( - REMOVE_ITEM Mayo_SourceFiles + REMOVE_ITEM MayoIO_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_base_mesh.cpp ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_gltf_reader.cpp ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_obj_reader.cpp @@ -383,18 +357,18 @@ if(OpenCASCADE_FOUND) endif() if(OpenCASCADE_VERSION VERSION_LESS 7.5.0) - list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_gltf_writer.cpp) + list(REMOVE_ITEM MayoIO_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_gltf_writer.cpp) message(STATUS "glTF writer disabled because OpenCascade < v7.5") endif() if(OpenCASCADE_VERSION VERSION_LESS 7.6.0) - list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_obj_writer.cpp) + list(REMOVE_ITEM MayoIO_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_obj_writer.cpp) message(STATUS "OBJ writer disabled because OpenCascade < v7.6") endif() # VRML support if(OpenCASCADE_VERSION VERSION_LESS 7.7.0) - list(REMOVE_ITEM Mayo_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_vrml_reader.cpp) + list(REMOVE_ITEM MayoIO_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_occ/io_occ_vrml_reader.cpp) message(STATUS "VRML reader disabled because OpenCascade < v7.7") endif() endif() @@ -420,10 +394,10 @@ if(assimp_FOUND) file(GLOB MayoPluginAssimp_HeaderFiles ${PROJECT_SOURCE_DIR}/src/io_assimp/*.h) file(GLOB MayoPluginAssimp_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_assimp/*.cpp) - list(APPEND Mayo_HeaderFiles ${MayoPluginAssimp_HeaderFiles}) - list(APPEND Mayo_SourceFiles ${MayoPluginAssimp_SourceFiles}) + list(APPEND MayoIO_HeaderFiles ${MayoPluginAssimp_HeaderFiles}) + list(APPEND MayoIO_SourceFiles ${MayoPluginAssimp_SourceFiles}) - list(APPEND Mayo_LinkLibraries ${ASSIMP_LIBRARIES}) + list(APPEND MayoIO_LinkLibraries ${ASSIMP_LIBRARIES}) list(GET ASSIMP_INCLUDE_DIRS 0 assimp_IncludeDir) file(READ "${assimp_IncludeDir}/assimp/version.h" assimp_FileVersionH) @@ -445,11 +419,11 @@ if(gmio_FOUND) file(GLOB MayoPluginGmio_HeaderFiles ${PROJECT_SOURCE_DIR}/src/io_gmio/*.h) file(GLOB MayoPluginGmio_SourceFiles ${PROJECT_SOURCE_DIR}/src/io_gmio/*.cpp) - list(APPEND Mayo_HeaderFiles ${MayoPluginGmio_HeaderFiles}) - list(APPEND Mayo_SourceFiles ${MayoPluginGmio_SourceFiles}) + list(APPEND MayoIO_HeaderFiles ${MayoPluginGmio_HeaderFiles}) + list(APPEND MayoIO_SourceFiles ${MayoPluginGmio_SourceFiles}) # Needs -L$$GMIO_ROOT/lib -lgmio_static -lzlibstatic - list(APPEND Mayo_LinkLibraries ${GMIO_LIBRARIES}) + list(APPEND MayoIO_LinkLibraries ${GMIO_LIBRARIES}) endif() ########## @@ -460,43 +434,233 @@ configure_file(${PROJECT_SOURCE_DIR}/src/mayo_config.h.cmake common/mayo_config configure_file(${PROJECT_SOURCE_DIR}/src/mayo_version.h.cmake common/mayo_version.h @ONLY) ########## -# Targets +# Icons for Mayo exe targets ########## if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(Mayo_AppIconMacOS images/appicon.icns) - set_source_files_properties(images/appicon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + set_source_files_properties(images/appicon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") endif() -add_executable( - mayo - ${Mayo_SourceFiles} - ${Mayo_HeaderFiles} - ${Mayo_QtUiFiles} - mayo.qrc i18n/qt${QT_VERSION_MAJOR}base.qrc - ${Mayo_RcIconsWin} - ${Mayo_AppIconMacOS} -) +if(MSVC) + set(Mayo_RcIconsWin ${PROJECT_SOURCE_DIR}/images/appicon.rc) +endif() + +########## +# Target: MayoCore(static lib) +########## + +add_library(MayoCore STATIC ${MayoCore_HeaderFiles} ${MayoCore_SourceFiles}) + +target_include_directories(MayoCore PRIVATE ${Mayo_IncludeDirectories}) +target_compile_definitions(MayoCore PRIVATE ${Mayo_CompileDefinitions}) +target_compile_options(MayoCore PRIVATE ${Mayo_CompileOptions}) +target_link_libraries(MayoCore PRIVATE ${MayoCore_LinkLibraries}) + +add_library(MayoCoreLib INTERFACE) +target_link_libraries(MayoCoreLib INTERFACE MayoCore ${MayoCore_LinkLibraries}) +target_link_directories(MayoCoreLib INTERFACE ${MayoCore_LinkDirectories}) +target_include_directories(MayoCoreLib INTERFACE ${Mayo_IncludeDirectories}) + +########## +# Target: MayoIO(static lib) +########## + +add_library(MayoIO STATIC ${MayoIO_HeaderFiles} ${MayoIO_SourceFiles}) + +target_include_directories(MayoIO PRIVATE ${Mayo_IncludeDirectories}) +target_compile_definitions(MayoIO PRIVATE ${Mayo_CompileDefinitions}) +target_compile_options(MayoIO PRIVATE ${Mayo_CompileOptions}) +target_link_libraries(MayoIO PRIVATE ${MayoIO_LinkLibraries}) + +add_library(MayoIOLib INTERFACE) +target_link_libraries(MayoIOLib INTERFACE MayoIO ${MayoIO_LinkLibraries}) +target_include_directories(MayoIOLib INTERFACE ${Mayo_IncludeDirectories}) + +########## +# Target: MayoApp +########## + +set(MayoApp_HeaderFiles) +set(MayoApp_SourceFiles) +set(MayoApp_LinkLibraries) -target_include_directories( - mayo PRIVATE ${Mayo_IncludeDirectories} +# MayoApp headers +file( + GLOB MayoApp_HeaderFiles + ${PROJECT_SOURCE_DIR}/src/app/*.h + ${PROJECT_SOURCE_DIR}/src/measure/*.h + ${PROJECT_SOURCE_DIR}/src/qtbackend/*.h + ${PROJECT_SOURCE_DIR}/src/qtcommon/*.h ) -target_compile_definitions( - mayo PRIVATE ${Mayo_CompileDefinitions} +if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) + list(APPEND MayoApp_HeaderFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.h) +endif() + +# MayoApp sources +file( + GLOB MayoApp_SourceFiles + ${PROJECT_SOURCE_DIR}/src/app/*.cpp + ${PROJECT_SOURCE_DIR}/src/measure/*.cpp + ${PROJECT_SOURCE_DIR}/src/qtbackend/*.cpp + ${PROJECT_SOURCE_DIR}/src/qtcommon/*.cpp ) -target_compile_options( - mayo PRIVATE ${Mayo_CompileOptions} +if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) + list(APPEND MayoApp_SourceFiles ${PROJECT_SOURCE_DIR}/src/app/windows/win_taskbar_global_progress.cpp) +endif() + +# MayoApp UI files +file(GLOB MayoApp_QtUiFiles ${PROJECT_SOURCE_DIR}/src/app/*.ui) + +# MayoApp unit tests +if(Mayo_BuildTests) + list(APPEND MayoApp_HeaderFiles ${MayoTests_HeaderFiles}) + list(APPEND MayoApp_SourceFiles ${MayoTests_SourceFiles}) + list(APPEND MayoApp_LinkLibraries ${MayoTests_LinkLibraries}) +endif() + +# MayoApp libs +list( + APPEND MayoApp_LinkLibraries + MayoCoreLib + MayoIOLib + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets ) -target_link_directories( - mayo PRIVATE ${Mayo_LinkDirectories} +if(WIN32 AND QT_VERSION_MAJOR EQUAL 5) + list(APPEND MayoApp_LinkLibraries Qt5::WinExtras) +elseif(QT_VERSION_MAJOR EQUAL 6) + list(APPEND MayoApp_LinkLibraries Qt6::OpenGLWidgets) +endif() + +if(Mayo_PostBuildCopyRuntimeDLLs) + list(APPEND MayoApp_LinkLibraries Qt${QT_VERSION_MAJOR}::Svg) +endif() + +if(Mayo_BuildApp) + add_executable( + mayo + ${MayoApp_SourceFiles} + ${MayoApp_HeaderFiles} + ${MayoApp_QtUiFiles} + mayo.qrc i18n/qt${QT_VERSION_MAJOR}base.qrc + ${Mayo_RcIconsWin} + ${Mayo_AppIconMacOS} + ) + + target_compile_definitions(mayo PRIVATE ${Mayo_CompileDefinitions}) + target_compile_options(mayo PRIVATE ${Mayo_CompileOptions}) + target_link_libraries(mayo PRIVATE ${MayoApp_LinkLibraries}) + + set_target_properties( + mayo + PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "mayo" + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE_COPYRIGHT ${Mayo_Copyright} + MACOSX_BUNDLE_ICON_FILE appicon.icns + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE + ) + + if(Mayo_PostBuildCopyRuntimeDLLs) + # Copy required Qt plugins + set(QtPluginsDir "${QT_DIR}/../../../plugins") + file(GLOB QtPluginIconEnginesDLLs "${QtPluginsDir}/iconengines/qsvgicon*.dll") + file(GLOB QtPluginImageFormatsDLLs "${QtPluginsDir}/imageformats/qsvg*.dll") + file(GLOB QtPluginPlatformsDLLs "${QtPluginsDir}/platforms/qwindows*.dll") + set(QtPluginsDLLs ${QtPluginIconEnginesDLLs} ${QtPluginImageFormatsDLLs} ${QtPluginPlatformsDLLs}) + foreach(QtPluginDLL ${QtPluginsDLLs}) + cmake_path(GET QtPluginDLL PARENT_PATH QtPluginDLL_Path) + cmake_path(GET QtPluginDLL_Path FILENAME QtPluginDLL_PathName) + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "$/plugins/${QtPluginDLL_PathName}" + ) + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${QtPluginDLL}" "$/plugins/${QtPluginDLL_PathName}" + ) + endforeach() + + # Copy OpenCascade 3rd-party DLLs + foreach(Occ3rdDLL ${OpenCASCADE_3RDPARTY_DLLS}) + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${Occ3rdDLL}" $ + ) + endforeach() + + # Copy runtime DLLs specified with library IMPORTED_LOCATION property + add_custom_command( + TARGET mayo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ $ + COMMAND_EXPAND_LISTS + ) + endif() # Mayo_PostBuildCopyRuntimeDLLs +endif() # Mayo_BuildApp + +########## +# Target: mayo-conv +########## + +file( + GLOB MayoConv_HeaderFiles + ${PROJECT_SOURCE_DIR}/src/cli/*.h + ${PROJECT_SOURCE_DIR}/src/qtbackend/*.h + ${PROJECT_SOURCE_DIR}/src/qtcommon/*.h +) +list( + APPEND MayoConv_HeaderFiles + ${PROJECT_SOURCE_DIR}/src/app/app_module_properties.h + ${PROJECT_SOURCE_DIR}/src/app/app_module.h + ${PROJECT_SOURCE_DIR}/src/app/recent_files.h ) -target_link_libraries( - mayo PRIVATE ${Mayo_LinkLibraries} +file( + GLOB MayoConv_SourceFiles + ${PROJECT_SOURCE_DIR}/src/cli/*.cpp + ${PROJECT_SOURCE_DIR}/src/qtbackend/*.cpp + ${PROJECT_SOURCE_DIR}/src/qtcommon/*.cpp ) +list( + APPEND MayoConv_SourceFiles + ${PROJECT_SOURCE_DIR}/src/app/app_module_properties.cpp + ${PROJECT_SOURCE_DIR}/src/app/app_module.cpp + ${PROJECT_SOURCE_DIR}/src/app/recent_files.cpp +) + +if(Mayo_BuildConvCli) + add_executable( + mayo-conv + ${MayoConv_HeaderFiles} + ${MayoConv_SourceFiles} + i18n/qt${QT_VERSION_MAJOR}base.qrc + ${Mayo_RcIconsWin} + ${Mayo_AppIconMacOS} + ) + + target_compile_definitions(mayo-conv PRIVATE ${Mayo_CompileDefinitions}) + target_compile_options(mayo-conv PRIVATE ${Mayo_CompileOptions}) + + target_link_libraries( + mayo-conv PRIVATE + MayoCoreLib + MayoIOLib + Qt${QT_VERSION_MAJOR}::Core + ) +endif() # Mayo_BuildConvCli + +########## +# Target: OtherFiles +########## add_custom_target( OtherFiles SOURCES @@ -511,53 +675,3 @@ add_custom_target( src/mayo_version.h.cmake README.md ) - -set_target_properties( - mayo - PROPERTIES - MACOSX_BUNDLE_BUNDLE_NAME "mayo" - MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} - MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - MACOSX_BUNDLE_COPYRIGHT ${Mayo_Copyright} - MACOSX_BUNDLE_ICON_FILE appicon.icns - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -if(Mayo_PostBuildCopyRuntimeDLLs) - # Copy required Qt plugins - set(QtPluginsDir "${QT_DIR}/../../../plugins") - file(GLOB QtPluginIconEnginesDLLs "${QtPluginsDir}/iconengines/qsvgicon*.dll") - file(GLOB QtPluginImageFormatsDLLs "${QtPluginsDir}/imageformats/qsvg*.dll") - file(GLOB QtPluginPlatformsDLLs "${QtPluginsDir}/platforms/qwindows*.dll") - set(QtPluginsDLLs ${QtPluginIconEnginesDLLs} ${QtPluginImageFormatsDLLs} ${QtPluginPlatformsDLLs}) - foreach(QtPluginDLL ${QtPluginsDLLs}) - cmake_path(GET QtPluginDLL PARENT_PATH QtPluginDLL_Path) - cmake_path(GET QtPluginDLL_Path FILENAME QtPluginDLL_PathName) - add_custom_command( - TARGET mayo POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "$/plugins/${QtPluginDLL_PathName}" - ) - add_custom_command( - TARGET mayo POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${QtPluginDLL}" "$/plugins/${QtPluginDLL_PathName}" - ) - endforeach() - - # Copy OpenCascade 3rd-party DLLs - foreach(Occ3rdDLL ${OpenCASCADE_3RDPARTY_DLLS}) - add_custom_command( - TARGET mayo POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${Occ3rdDLL}" $ - ) - endforeach() - - # Copy runtime DLLs specified with library IMPORTED_LOCATION property - add_custom_command( - TARGET mayo POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ $ - COMMAND_EXPAND_LISTS - ) -endif() diff --git a/cmake/OpenCascadeWin.cmake b/cmake/OpenCascadeWin.cmake index 138783c7..3dc52174 100644 --- a/cmake/OpenCascadeWin.cmake +++ b/cmake/OpenCascadeWin.cmake @@ -120,17 +120,6 @@ if(OpenCASCADE_FOUND) ) endforeach() - # Add OpenCASCADE library directories - # Note: QtCreator option "Run->Add build library search to PATH" will add to PATH env variable - # the contents of Mayo_LinkDirectories variable. For convenience, let's add also the - # directories containing DLLs - list( - APPEND Mayo_LinkDirectories - ${OpenCASCADE_BINARY_DIR} - ${OpenCASCADE_LIBRARY_DIR} - ${OpenCASCADE_3RDPARTY_BINARY_DIRS} - ) - # List all 3rd-party DLLs required by OpenCASCADE set(OpenCASCADE_3RDPARTY_DLLS) foreach(OccBINDIR ${OpenCASCADE_3RDPARTY_BINARY_DIRS}) diff --git a/src/app/app_module.cpp b/src/app/app_module.cpp index efa02e97..60bc3f71 100644 --- a/src/app/app_module.cpp +++ b/src/app/app_module.cpp @@ -15,21 +15,72 @@ #include "../base/settings.h" #include "../gui/gui_application.h" #include "../gui/gui_document.h" -#include "qtcore_utils.h" -#include "filepath_conv.h" -#include "qstring_conv.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" +#include "../qtcommon/qtcore_utils.h" #include +#include #include #include -#include #include #include namespace Mayo { +namespace { + +void readRecentFile(QDataStream& stream, RecentFile* recentFile) +{ + QString strFilepath; + stream >> strFilepath; + if (stream.status() != QDataStream::Ok) + return; + + recentFile->filepath = filepathFrom(strFilepath); + stream >> recentFile->thumbnail.imageData; + if (stream.status() != QDataStream::Ok) + return; + + recentFile->thumbnail.imageCacheKey = -1; + // Read thumbnail timestamp + // Warning: qint64 and int64_t may not be the exact same type(eg __int64 and longlong with Windows/MSVC) + qint64 timestamp; + stream >> timestamp; + if (stream.status() != QDataStream::Ok) + return; + + recentFile->thumbnailTimestamp = timestamp; +} + +QuantityLength shapeChordalDeflection(const TopoDS_Shape& shape) +{ + // Excerpted from Prs3d::GetDeflection(...) + constexpr QuantityLength baseDeviation = 1 * Quantity_Millimeter; + + Bnd_Box bndBox; + constexpr bool useTriangulation = true; + BRepBndLib::Add(shape, bndBox, !useTriangulation); + if (bndBox.IsVoid()) + return baseDeviation; + + if (BndUtils::isOpen(bndBox)) { + if (!BndUtils::hasFinitePart(bndBox)) + return baseDeviation; + + bndBox = BndUtils::finitePart(bndBox); + } + + const auto coords = BndBoxCoords::get(bndBox); + const gp_XYZ diag = coords.maxVertex().XYZ() - coords.minVertex().XYZ(); + const double diagMaxComp = std::max({ diag.X(), diag.Y(), diag.Z() }); + return 4 * diagMaxComp * baseDeviation; +} + +} // namespace + AppModule::AppModule() : m_settings(new Settings), m_props(m_settings), @@ -111,7 +162,7 @@ Settings::Variant AppModule::toVariant(const Property& prop) const const auto& filesProp = constRef(prop); QByteArray blob; QDataStream stream(&blob, QIODevice::WriteOnly); - stream << filesProp.value(); + AppModule::writeRecentFiles(stream, filesProp.value()); Variant varBlob(blob.toStdString()); varBlob.setByteArray(true); return varBlob; @@ -124,13 +175,10 @@ Settings::Variant AppModule::toVariant(const Property& prop) const bool AppModule::fromVariant(Property* prop, const Settings::Variant& variant) const { if (isType(prop)) { - if (qobject_cast(QCoreApplication::instance()) == nullptr) - return true; - const QByteArray blob = QtCoreUtils::QByteArray_frowRawData(variant.toConstRefString()); QDataStream stream(blob); RecentFiles recentFiles; - stream >> recentFiles; + AppModule::readRecentFiles(stream, &recentFiles); ptr(prop)->setValue(recentFiles); return stream.status() == QDataStream::Ok; } @@ -201,12 +249,13 @@ void AppModule::recordRecentFileThumbnail(GuiDocument* guiDoc) const RecentFile* recentFile = this->findRecentFile(guiDoc->document()->filePath()); if (!recentFile) { - qDebug() << fmt::format("RecentFile object is null\n" - " Function: {}\n Document: {}\n RecentFilesCount: {}", - Q_FUNC_INFO, - guiDoc->document()->filePath().u8string(), - m_props.recentFiles.value().size()) - .c_str(); + qDebug() << fmt::format( + "RecentFile object is null\n" + " Function: {}\n Document: {}\n RecentFilesCount: {}", + Q_FUNC_INFO, + guiDoc->document()->filePath().u8string(), + m_props.recentFiles.value().size() + ).c_str(); return; } @@ -214,7 +263,7 @@ void AppModule::recordRecentFileThumbnail(GuiDocument* guiDoc) return; RecentFile newRecentFile = *recentFile; - const bool okRecord = newRecentFile.recordThumbnail(guiDoc, this->recentFileThumbnailSize()); + const bool okRecord = this->impl_recordRecentFileThumbnail(&newRecentFile, guiDoc); if (!okRecord) return; @@ -238,7 +287,7 @@ void AppModule::recordRecentFileThumbnails(GuiApplication* guiApp) continue; // Skip RecentFile newRecentFile = *recentFile; - if (newRecentFile.recordThumbnail(guiDoc, this->recentFileThumbnailSize())) { + if (this->impl_recordRecentFileThumbnail(&newRecentFile, guiDoc)) { auto indexRecentFile = std::distance(&listRecentFile.front(), recentFile); newListRecentFile.at(indexRecentFile) = newRecentFile; } @@ -247,28 +296,51 @@ void AppModule::recordRecentFileThumbnails(GuiApplication* guiApp) m_props.recentFiles.setValue(newListRecentFile); } -static QuantityLength shapeChordalDeflection(const TopoDS_Shape& shape) +void AppModule::setRecentFileThumbnailRecorder(std::function fn) { - // Excerpted from Prs3d::GetDeflection(...) - constexpr QuantityLength baseDeviation = 1 * Quantity_Millimeter; + m_fnRecentFileThumbnailRecorder = std::move(fn); +} - Bnd_Box bndBox; - constexpr bool useTriangulation = true; - BRepBndLib::Add(shape, bndBox, !useTriangulation); - if (bndBox.IsVoid()) - return baseDeviation; - if (BndUtils::isOpen(bndBox)) { - if (!BndUtils::hasFinitePart(bndBox)) - return baseDeviation; +void AppModule::readRecentFiles(QDataStream& stream, RecentFiles* recentFiles) +{ + auto fnCheckStreamStatus = [](QDataStream::Status status) { + if (status != QDataStream::Ok) { + qDebug() << fmt::format( + "QDataStream error\n Function: {}\n Status: {}", + Q_FUNC_INFO, MetaEnum::name(status) + ).c_str(); + return false; + } - bndBox = BndUtils::finitePart(bndBox); + return true; + }; + + uint32_t count = 0; + stream >> count; + if (!fnCheckStreamStatus(stream.status())) + return; // Stream extraction error, abort + + recentFiles->clear(); + for (uint32_t i = 0; i < count; ++i) { + RecentFile recent; + readRecentFile(stream, &recent); + if (!fnCheckStreamStatus(stream.status())) + return; // Stream extraction error, abort + + if (!recent.filepath.empty() && recent.thumbnailTimestamp != 0) + recentFiles->push_back(std::move(recent)); } +} - const auto coords = BndBoxCoords::get(bndBox); - const gp_XYZ diag = coords.maxVertex().XYZ() - coords.minVertex().XYZ(); - const double diagMaxComp = std::max({ diag.X(), diag.Y(), diag.Z() }); - return 4 * diagMaxComp * baseDeviation; +void AppModule::writeRecentFiles(QDataStream& stream, const RecentFiles& recentFiles) +{ + stream << uint32_t(recentFiles.size()); + for (const RecentFile& rf : recentFiles) { + stream << filepathTo(rf.filepath); + stream << rf.thumbnail.imageData; + stream << qint64(rf.thumbnailTimestamp); + } } OccBRepMeshParameters AppModule::brepMeshParameters(const TopoDS_Shape& shape) const @@ -347,4 +419,34 @@ AppModule::~AppModule() m_settings = nullptr; } +bool AppModule::impl_recordRecentFileThumbnail(RecentFile* recentFile, GuiDocument* guiDoc) +{ + if (!recentFile) + return false; + + if (!guiDoc) + return false; + + if (!m_fnRecentFileThumbnailRecorder) + return false; + + if (!filepathEquivalent(recentFile->filepath, guiDoc->document()->filePath())) { + qDebug() << fmt::format( + "Filepath mismatch with GUI document\n" + " Function: {}\n Filepath: {}\n Document: {}", + Q_FUNC_INFO, + recentFile->filepath.u8string(), + guiDoc->document()->filePath().u8string() + ).c_str(); + return false; + } + + if (recentFile->thumbnailTimestamp == RecentFile::timestampLastModified(recentFile->filepath)) + return true; + + recentFile->thumbnail = m_fnRecentFileThumbnailRecorder(guiDoc, this->recentFileThumbnailSize()); + recentFile->thumbnailTimestamp = RecentFile::timestampLastModified(recentFile->filepath); + return true; +} + } // namespace Mayo diff --git a/src/app/app_module.h b/src/app/app_module.h index 42e02b67..c9572de8 100644 --- a/src/app/app_module.h +++ b/src/app/app_module.h @@ -18,9 +18,12 @@ #include "../base/settings.h" #include "../base/unit_system.h" +#include + #include #include +class QDataStream; class TDF_Label; class TopoDS_Shape; @@ -83,6 +86,9 @@ class AppModule : void recordRecentFileThumbnail(GuiDocument* guiDoc); void recordRecentFileThumbnails(GuiApplication* guiApp); QSize recentFileThumbnailSize() const { return { 190, 150 }; } + void setRecentFileThumbnailRecorder(std::function fn); + static void readRecentFiles(QDataStream& stream, RecentFiles* recentFiles); + static void writeRecentFiles(QDataStream& stream, const RecentFiles& recentFiles); // Meshing of BRep shapes OccBRepMeshParameters brepMeshParameters(const TopoDS_Shape& shape) const; @@ -113,6 +119,8 @@ class AppModule : AppModule(const AppModule&) = delete; // Not copyable AppModule& operator=(const AppModule&) = delete; // Not copyable + bool impl_recordRecentFileThumbnail(RecentFile* recentFile, GuiDocument* guiDoc); + Settings* m_settings = nullptr; IO::System m_ioSystem; AppModuleProperties m_props; @@ -121,6 +129,7 @@ class AppModule : std::locale m_stdLocale; QLocale m_qtLocale; std::vector> m_vecDocTreeNodePropsProvider; + std::function m_fnRecentFileThumbnailRecorder; }; } // namespace Mayo diff --git a/src/app/app_module_properties.cpp b/src/app/app_module_properties.cpp index e513e7ed..1374fc66 100644 --- a/src/app/app_module_properties.cpp +++ b/src/app/app_module_properties.cpp @@ -91,7 +91,7 @@ AppModuleProperties::AppModuleProperties(Settings* settings) #endif }); settings->addResetFunction(groupId_graphics, [=]{ - this->navigationStyle.setValue(WidgetOccViewController::NavigationStyle::Mayo); + this->navigationStyle.setValue(View3dNavigationStyle::Mayo); this->defaultShowOriginTrihedron.setValue(true); this->instantZoomFactor.setValue(5.); this->turnViewAngleIncrement.setQuantity(5 * Quantity_Degree); diff --git a/src/app/app_module_properties.h b/src/app/app_module_properties.h index d8874672..8440c7f7 100644 --- a/src/app/app_module_properties.h +++ b/src/app/app_module_properties.h @@ -15,7 +15,7 @@ #include "../base/property_enumeration.h" #include "../base/settings.h" #include "../base/unit_system.h" -#include "widget_occ_view_controller.h" +#include "view3d_navigation_style.h" #include #include @@ -60,7 +60,7 @@ class AppModuleProperties : public PropertyGroup { PropertyAngle meshingAngularDeflection{ this, textId("meshingAngularDeflection") }; PropertyBool meshingRelative{ this, textId("meshingRelative") }; // Graphics - PropertyEnum navigationStyle{ this, textId("navigationStyle") }; + PropertyEnum navigationStyle{ this, textId("navigationStyle") }; PropertyBool defaultShowOriginTrihedron{ this, textId("defaultShowOriginTrihedron") }; PropertyDouble instantZoomFactor{ this, textId("instantZoomFactor") }; PropertyAngle turnViewAngleIncrement{ this, textId("turnViewAngleIncrement") }; diff --git a/src/app/command_system_information.cpp b/src/app/command_system_information.cpp index 05017e43..03525e97 100644 --- a/src/app/command_system_information.cpp +++ b/src/app/command_system_information.cpp @@ -8,11 +8,11 @@ #include "app_module.h" #include "command_system_information_occopengl.h" -#include "qstring_conv.h" #include "qtwidgets_utils.h" #include "../base/meta_enum.h" #include "../base/filepath.h" #include "../base/io_system.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/commands_display.cpp b/src/app/commands_display.cpp index 9cdea974..04e99a7b 100644 --- a/src/app/commands_display.cpp +++ b/src/app/commands_display.cpp @@ -12,8 +12,8 @@ #include "../gui/gui_application.h" #include "../gui/gui_document.h" #include "../gui/v3d_view_controller.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" -#include "qstring_conv.h" #include "theme.h" #include diff --git a/src/app/commands_file.cpp b/src/app/commands_file.cpp index a4951455..a4ce96ea 100644 --- a/src/app/commands_file.cpp +++ b/src/app/commands_file.cpp @@ -9,9 +9,9 @@ #include "../base/application.h" #include "../base/task_manager.h" #include "../gui/gui_application.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" -#include "filepath_conv.h" -#include "qstring_conv.h" #include "recent_files.h" #include "theme.h" diff --git a/src/app/commands_help.cpp b/src/app/commands_help.cpp index 92cb882e..47430bca 100644 --- a/src/app/commands_help.cpp +++ b/src/app/commands_help.cpp @@ -8,7 +8,7 @@ #include "dialog_about.h" #include "qtwidgets_utils.h" -#include "qstring_conv.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/dialog_about.cpp b/src/app/dialog_about.cpp index d117a6ba..3c474d6a 100644 --- a/src/app/dialog_about.cpp +++ b/src/app/dialog_about.cpp @@ -7,7 +7,7 @@ #include "dialog_about.h" #include "ui_dialog_about.h" -#include "qstring_conv.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/dialog_inspect_xde.cpp b/src/app/dialog_inspect_xde.cpp index 4bdff451..d4c0e85f 100644 --- a/src/app/dialog_inspect_xde.cpp +++ b/src/app/dialog_inspect_xde.cpp @@ -12,10 +12,10 @@ #include "../base/meta_enum.h" #include "../base/settings.h" #include "../base/tkernel_utils.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" -#include "filepath_conv.h" #include "qmeta_tdf_label.h" -#include "qstring_conv.h" #include "qstring_utils.h" #include "qtgui_utils.h" #include "qtwidgets_utils.h" diff --git a/src/app/dialog_options.cpp b/src/app/dialog_options.cpp index 10f147d4..8fd8de92 100644 --- a/src/app/dialog_options.cpp +++ b/src/app/dialog_options.cpp @@ -10,10 +10,10 @@ #include "../base/settings.h" #include "../base/property_builtins.h" #include "../base/property_enumeration.h" +#include "../qtbackend/qsettings_storage.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" #include "item_view_buttons.h" -#include "qsettings_storage.h" -#include "qstring_conv.h" #include "qtgui_utils.h" #include "qtwidgets_utils.h" #include "theme.h" @@ -372,7 +372,8 @@ void DialogOptions::saveAs() { const QString startDirPath = QString(); const QString filepath = QFileDialog::getSaveFileName( - this, tr("Choose INI file"), startDirPath, tr("INI files(*.ini)")); + this, tr("Choose INI file"), startDirPath, tr("INI files(*.ini)") + ); if (filepath.isEmpty()) return; diff --git a/src/app/dialog_task_manager.cpp b/src/app/dialog_task_manager.cpp index fc195275..6ea2d201 100644 --- a/src/app/dialog_task_manager.cpp +++ b/src/app/dialog_task_manager.cpp @@ -9,9 +9,9 @@ #include "../base/application.h" #include "../base/settings.h" #include "../base/task_manager.h" +#include "../qtcommon/qstring_conv.h" #include "ui_dialog_task_manager.h" #include "app_module.h" -#include "qstring_conv.h" #include "qstring_utils.h" #include "theme.h" diff --git a/src/app/document_property_group.cpp b/src/app/document_property_group.cpp index 24b987b7..98142e03 100644 --- a/src/app/document_property_group.cpp +++ b/src/app/document_property_group.cpp @@ -7,10 +7,10 @@ #include "document_property_group.h" #include "app_module.h" -#include "filepath_conv.h" -#include "qstring_conv.h" #include "qstring_utils.h" #include "../base/application.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/document_tree_node_properties_providers.cpp b/src/app/document_tree_node_properties_providers.cpp index 94db4dcc..70f7950f 100644 --- a/src/app/document_tree_node_properties_providers.cpp +++ b/src/app/document_tree_node_properties_providers.cpp @@ -19,7 +19,7 @@ #include "../graphics/graphics_mesh_object_driver.h" #include "../graphics/graphics_point_cloud_object_driver.h" #include "../graphics/graphics_shape_object_driver.h" -#include "qstring_conv.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/gui_document_list_model.cpp b/src/app/gui_document_list_model.cpp index 7ebb5f9c..87b8b31b 100644 --- a/src/app/gui_document_list_model.cpp +++ b/src/app/gui_document_list_model.cpp @@ -6,12 +6,12 @@ #include "gui_document_list_model.h" -#include "filepath_conv.h" -#include "qstring_conv.h" #include "../base/application.h" #include "../base/document.h" #include "../gui/gui_application.h" #include "../gui/gui_document.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" namespace Mayo { diff --git a/src/app/main.cpp b/src/app/main.cpp index ec9d611d..8d10a26d 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -20,28 +20,30 @@ #include "../graphics/graphics_mesh_object_driver.h" #include "../graphics/graphics_point_cloud_object_driver.h" #include "../graphics/graphics_shape_object_driver.h" +#include "../graphics/graphics_utils.h" #include "../gui/gui_application.h" +#include "../qtbackend/qt_app_translator.h" +#include "../qtbackend/qt_signal_thread_helper.h" +#include "../qtbackend/qsettings_storage.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/log_message_handler.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" -#include "cli_export.h" #include "commands_help.h" -#include "console.h" #include "document_tree_node_properties_providers.h" -#include "filepath_conv.h" #include "mainwindow.h" -#include "qsettings_storage.h" -#include "qstring_conv.h" #include "qtgui_utils.h" #include "theme.h" #include "widget_model_tree.h" #include "widget_model_tree_builder_mesh.h" #include "widget_model_tree_builder_xde.h" #include "widget_occ_view.h" +#include #include #include #include #include -#include #include #include #include @@ -49,6 +51,7 @@ #include #include #include +#include #include #include @@ -58,10 +61,6 @@ #include #include -#ifdef Q_OS_WIN -# include // For AttachConsole(), etc. -#endif - namespace Mayo { // Declared in graphics/graphics_create_driver.cpp @@ -81,100 +80,10 @@ struct CommandLineArguments { FilePath filepathSettings; FilePath filepathLog; bool includeDebugLogs = true; - std::vector listFilepathToExport; std::vector listFilepathToOpen; - bool cliProgressReport = true; bool showSystemInformation = false; }; -// Provides customization of Qt message handler -class LogMessageHandler { -public: - static LogMessageHandler& instance() - { - static LogMessageHandler object; - return object; - } - - // Corresponds to CommandLineArguments::includeDebugLogs - void enableDebugLogs(bool on) - { - m_enableDebugLogs = on; - } - - // Corresponds to CommandLineArguments::filepathLog - void setOutputFilePath(const FilePath& fp) - { - m_outputFilePath = fp; - if (!fp.empty()) - m_outputFile.open(fp, std::ios::out | std::ios::app); - else - m_outputFile.close(); - } - - std::ostream& outputStream(QtMsgType type) - { - if (!m_outputFilePath.empty() && m_outputFile.is_open()) - return m_outputFile; - - if (type == QtDebugMsg || type == QtInfoMsg) - return std::cout; - - return std::cerr; - } - - // Function called for Qt message handling - static void qtHandler(QtMsgType type, const QMessageLogContext& /*context*/, const QString& msg) - { - const std::string localMsg = consoleToPrintable(msg); - std::ostream& outs = LogMessageHandler::instance().outputStream(type); - switch (type) { - case QtDebugMsg: - if (LogMessageHandler::instance().m_enableDebugLogs) { - outs << "DEBUG: " << localMsg << std::endl; - } - break; - case QtInfoMsg: - outs << "INFO: " << localMsg << std::endl; - break; - case QtWarningMsg: - outs << "WARNING: " << localMsg << std::endl; - break; - case QtCriticalMsg: - outs << "CRITICAL: " << localMsg << std::endl; - break; - case QtFatalMsg: - outs << "FATAL: " << localMsg << std::endl; - break; - } - } - -private: - LogMessageHandler() = default; - - FilePath m_outputFilePath; - std::ofstream m_outputFile; - bool m_enableDebugLogs = true; -}; - -// Provides handling of signal/slot thread mismatch with the help of Qt -// There will be a single QObject created per thread, so it can be used to enqueue slot functions -class QtSignalThreadHelper : public ISignalThreadHelper { -public: - std::any getCurrentThreadContext() override - { - // Note: thread_local implies "static" - // See https://en.cppreference.com/w/cpp/language/storage_duration - thread_local QObject obj; - return &obj; - } - - void execInThread(const std::any& context, const std::function& fn) override - { - QTimer::singleShot(0, std::any_cast(context), fn); - } -}; - } // namespace // Parses command line and process Qt builtin options(basically --version and --help) @@ -204,14 +113,6 @@ static CommandLineArguments processCommandLine() ); cmdParser.addOption(cmdFileSettings); - const QCommandLineOption cmdFileToExport( - QStringList{ "e", "export" }, - Main::tr("Export opened files into an output file, can be repeated for different " - "formats(eg. -e file.stp -e file.igs...)"), - Main::tr("filepath") - ); - cmdParser.addOption(cmdFileToExport); - const QCommandLineOption cmdFileLog( QStringList{ "log-file" }, Main::tr("Writes log messages into output file"), @@ -225,12 +126,6 @@ static CommandLineArguments processCommandLine() ); cmdParser.addOption(cmdDebugLogs); - const QCommandLineOption cmdCliNoProgress( - QStringList{ "no-progress" }, - Main::tr("Disable progress reporting in console output(CLI-mode only)") - ); - cmdParser.addOption(cmdCliNoProgress); - const QCommandLineOption cmdSysInfo( QStringList{ "system-info" }, Main::tr("Show detailed system information and quit") @@ -264,11 +159,6 @@ static CommandLineArguments processCommandLine() if (cmdParser.isSet(cmdFileLog)) args.filepathLog = filepathFrom(cmdParser.value(cmdFileLog)); - if (cmdParser.isSet(cmdFileToExport)) { - for (const QString& strFilepath : cmdParser.values(cmdFileToExport)) - args.listFilepathToExport.push_back(filepathFrom(strFilepath)); - } - for (const QString& posArg : cmdParser.positionalArguments()) args.listFilepathToOpen.push_back(filepathFrom(posArg)); @@ -276,7 +166,6 @@ static CommandLineArguments processCommandLine() // By default this will exclude debug logs in release build args.includeDebugLogs = cmdParser.isSet(cmdDebugLogs); #endif - args.cliProgressReport = !cmdParser.isSet(cmdCliNoProgress); args.showSystemInformation = cmdParser.isSet(cmdSysInfo); return args; @@ -332,25 +221,6 @@ static void initOpenCascadeEnvironment(const FilePath& settingsFilepath) } } -// Function called by the Application i18n system, see Application::addTranslator() -static std::string_view qtTranslate(const TextId& text, int n) -{ - const QString qstr = QCoreApplication::translate(text.trContext.data(), text.key.data(), nullptr, n); - auto qstrHash = qHash(qstr); - static std::unordered_map mapStr; - static QReadWriteLock mapStrLock; - { - QReadLocker locker(&mapStrLock); - auto it = mapStr.find(qstrHash); - if (it != mapStr.cend()) - return it->second; - } - - QWriteLocker locker(&mapStrLock); - auto [it, ok] = mapStr.insert({ qstrHash, to_stdString(qstr) }); - return ok ? it->second : std::string_view{}; -} - // Helper to query the OpenGL version string [[maybe_unused]] static std::string queryGlVersionString() { @@ -388,6 +258,28 @@ static std::string_view qtTranslate(const TextId& text, int n) return QVersionNumber(versionMajor, versionMinor); } +Thumbnail createGuiDocumentThumbnail(GuiDocument* guiDoc, QSize size) +{ + Thumbnail thumbnail; + + IO::ImageWriter::Parameters params; + params.width = size.width(); + params.height = size.height(); + params.backgroundColor = QtGuiUtils::toPreferredColorSpace(mayoTheme()->color(Theme::Color::Palette_Window)); + Handle_Image_AlienPixMap pixmap = IO::ImageWriter::createImage(guiDoc, params); + if (!pixmap) { + qDebug() << "Empty pixmap returned by IO::ImageWriter::createImage()"; + return thumbnail; + } + + GraphicsUtils::ImagePixmap_flipY(*pixmap); + Image_PixMap::SwapRgbaBgra(*pixmap); + const QPixmap qPixmap = QtGuiUtils::toQPixmap(*pixmap); + thumbnail.imageData = QtGuiUtils::toQByteArray(qPixmap); + thumbnail.imageCacheKey = qPixmap.cacheKey(); + return thumbnail; +} + // Initializes "GUI" objects static void initGui(GuiApplication* guiApp) { @@ -459,6 +351,7 @@ static int runApp(QCoreApplication* qtApp) // Initialize AppModule auto appModule = AppModule::get(); appModule->settings()->setStorage(std::make_unique()); + appModule->setRecentFileThumbnailRecorder(&createGuiDocumentThumbnail); { // Load translation files auto fnLoadQmFile = [=](const QString& qmFilePath) { @@ -475,7 +368,7 @@ static int runApp(QCoreApplication* qtApp) // Initialize Base application auto app = Application::instance().get(); - app->addTranslator(&qtTranslate); // Set Qt i18n backend + app->addTranslator(&qtAppTranslate); // Set Qt i18n backend initOpenCascadeEnvironment("opencascade.conf"); // Initialize Gui application @@ -518,23 +411,6 @@ static int runApp(QCoreApplication* qtApp) return qtApp->exec(); } - if (!args.listFilepathToExport.empty()) { - if (args.listFilepathToOpen.empty()) - fnCriticalExit(Main::tr("No input files -> nothing to export")); - - guiApp->setAutomaticDocumentMapping(false); // GuiDocument objects aren't needed - appModule->settings()->resetAll(); - fnLoadAppSettings(appModule->settings()); - QTimer::singleShot(0, qtApp, [=]{ - CliExportArgs cliArgs; - cliArgs.progressReport = args.cliProgressReport; - cliArgs.filesToOpen = args.listFilepathToOpen; - cliArgs.filesToExport = args.listFilepathToExport; - cli_asyncExportDocuments(app, cliArgs, [=](int retcode) { qtApp->exit(retcode); }); - }); - return qtApp->exec(); - } - // Record recent files when documents are closed guiApp->signalGuiDocumentErased.connectSlot(&AppModule::recordRecentFileThumbnail, AppModule::get()); @@ -594,22 +470,13 @@ int main(int argc, char* argv[]) return false; }; - // If the arguments(argv) contain any of the following option, then Mayo has to run in CLI mode - const bool isAppCliMode = fnArgsContainAnyOf({ "-e", "--export", "-h", "--help", "-v", "--version" }); - // OpenCascade TKOpenGl depends on XLib for Linux(excepting Android) and BSD systems(excepting macOS) // See for example implementation of Aspect_DisplayConnection where XLib is explicitly used // On systems running eg Wayland this would cause problems(see https://github.com/fougue/mayo/issues/178) // As a workaround the Qt platform is forced to xcb #if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || (defined(Q_OS_BSD4) && !defined(Q_OS_MACOS)) - if ( - !isAppCliMode - && !qEnvironmentVariableIsSet("QT_QPA_PLATFORM") - && !fnArgsContainAnyOf({ "-platform" }) - ) - { + if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM") && !fnArgsContainAnyOf({ "-platform" })) qputenv("QT_QPA_PLATFORM", "xcb"); - } #endif // Configure and create Qt application object @@ -621,11 +488,7 @@ int main(int argc, char* argv[]) QCoreApplication::setOrganizationDomain("www.fougue.pro"); QCoreApplication::setApplicationName("Mayo"); QCoreApplication::setApplicationVersion(QString::fromUtf8(Mayo::strVersion)); - std::unique_ptr ptrApp( - isAppCliMode ? new QCoreApplication(argc, argv) : new QApplication(argc, argv) - ); - - //QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication app(argc, argv); // Handle unit tests #ifdef MAYO_WITH_TESTS @@ -633,26 +496,6 @@ int main(int argc, char* argv[]) return Mayo::runTests(argc, argv); #endif - // Configure for CLI mode - if (isAppCliMode) { -#if defined(Q_OS_WIN) && defined(NDEBUG) - qAddPostRoutine(&Mayo::consoleSendEnterKey); - // https://devblogs.microsoft.com/oldnewthing/20090101-00/?p=19643 - // https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/ - if (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) { - auto fnRedirectToConsole = [](DWORD hnd, FILE* file, const char* strPath) { - if (GetStdHandle(hnd) != INVALID_HANDLE_VALUE) { - file = freopen(strPath, "w", file); - setvbuf(file, nullptr, _IONBF, 0); - } - }; - fnRedirectToConsole(STD_OUTPUT_HANDLE, stdout, "CONOUT$"); - fnRedirectToConsole(STD_ERROR_HANDLE, stderr, "CONOUT$"); - std::ios::sync_with_stdio(); - } -#endif - } - - // Run Mayo application in CLI or GUI mode - return Mayo::runApp(ptrApp.get()); + // Run Mayo application GUI + return Mayo::runApp(&app); } diff --git a/src/app/property_editor_factory.cpp b/src/app/property_editor_factory.cpp index 57577344..ec48e18a 100644 --- a/src/app/property_editor_factory.cpp +++ b/src/app/property_editor_factory.cpp @@ -10,10 +10,10 @@ #include "../base/property_builtins.h" #include "../base/property_enumeration.h" #include "../base/unit_system.h" +#include "../qtcommon/qstring_conv.h" +#include "../qtcommon/qtcore_utils.h" #include "app_module.h" -#include "qstring_conv.h" #include "qstring_utils.h" -#include "qtcore_utils.h" #include "qtgui_utils.h" #include "qtwidgets_utils.h" diff --git a/src/app/property_item_delegate.cpp b/src/app/property_item_delegate.cpp index 57f3fc78..ad1a9c9b 100644 --- a/src/app/property_item_delegate.cpp +++ b/src/app/property_item_delegate.cpp @@ -9,10 +9,10 @@ #include "../base/property_builtins.h" #include "../base/settings.h" #include "../base/unit_system.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" -#include "filepath_conv.h" #include "qmeta_property.h" -#include "qstring_conv.h" #include "qstring_utils.h" #include "qtgui_utils.h" #include "theme.h" diff --git a/src/app/qtgui_utils.cpp b/src/app/qtgui_utils.cpp index 8b3dcb1a..d5690f74 100644 --- a/src/app/qtgui_utils.cpp +++ b/src/app/qtgui_utils.cpp @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -23,12 +24,14 @@ namespace QtGuiUtils { namespace { -constexpr bool isLessOrEqual(double lhs, double rhs) { +constexpr bool isLessOrEqual(double lhs, double rhs) +{ return lhs < rhs || qFuzzyCompare(lhs, rhs); } // Is 'v' inside ['start', 'end'] ? -constexpr bool isInRange(double v, double start, double end) { +constexpr bool isInRange(double v, double start, double end) +{ return isLessOrEqual(start, v) && isLessOrEqual(v, end); } @@ -53,10 +56,10 @@ QColor lerp(const QColor& a, const QColor& b, double t) { Expects(isInRange(t, 0, 1)); return QColor( - MathUtils::lerp(a.red(), b.red(), t), - MathUtils::lerp(a.green(), b.green(), t), - MathUtils::lerp(a.blue(), b.blue(), t), - MathUtils::lerp(a.alpha(), b.alpha(), t) + MathUtils::lerp(a.red(), b.red(), t), + MathUtils::lerp(a.green(), b.green(), t), + MathUtils::lerp(a.blue(), b.blue(), t), + MathUtils::lerp(a.alpha(), b.alpha(), t) ); } @@ -77,8 +80,9 @@ QColor linearColorAt(const QGradient& gradient, double t) ); if (itLower != stops.cbegin() && itLower != stops.cend()) { const int i = (itLower - stops.cbegin()) - 1; - const double tMapped = - MathUtils::mappedValue(t, stops.at(i).first, stops.at(i + 1).first, 0, 1); + const double tMapped = MathUtils::mappedValue( + t, stops.at(i).first, stops.at(i + 1).first, 0, 1 + ); return lerp(stops.at(i).second, stops.at(i + 1).second, tMapped); } @@ -143,20 +147,24 @@ int screenPixelHeight(double screenRatio, const QScreen* screen) QSize screenPixelSize(double widthRatio, double heightRatio, const QScreen* screen) { - return QSize(QtGuiUtils::screenPixelWidth(widthRatio, screen), - QtGuiUtils::screenPixelHeight(heightRatio, screen)); + return QSize( + QtGuiUtils::screenPixelWidth(widthRatio, screen), + QtGuiUtils::screenPixelHeight(heightRatio, screen) + ); } FontChange::FontChange(const QFont& font) : m_font(font) {} -FontChange& FontChange::size(int size) { +FontChange& FontChange::size(int size) +{ m_font.setPixelSize(size); return *this; } -FontChange& FontChange::adjustSize(int offset) { +FontChange& FontChange::adjustSize(int offset) +{ const int pixelSize = m_font.pixelSize() > 0 ? m_font.pixelSize() : 12; m_font.setPixelSize(pixelSize + offset); return *this; @@ -168,12 +176,14 @@ FontChange& FontChange::scalePointSizeF(double f) { return *this; } -FontChange& FontChange::bold(bool on) { +FontChange& FontChange::bold(bool on) +{ m_font.setBold(on); return *this; } -FontChange& FontChange::fixedPitch(bool on) { +FontChange& FontChange::fixedPitch(bool on) +{ m_font.setFixedPitch(on); return *this; } @@ -200,16 +210,34 @@ QPixmap toQPixmap(const Image_PixMap& pixmap) default: return QImage::Format_Invalid; } }; - const QImage img(pixmap.Data(), - int(pixmap.Width()), - int(pixmap.Height()), - int(pixmap.SizeRowBytes()), - fnToQImageFormat(pixmap.Format())); + const QImage img( + pixmap.Data(), + int(pixmap.Width()), + int(pixmap.Height()), + int(pixmap.SizeRowBytes()), + fnToQImageFormat(pixmap.Format()) + ); if (img.isNull()) return {}; return QPixmap::fromImage(img); } +QPixmap toQPixmap(const QByteArray& bytes, Qt::ImageConversionFlags flags) +{ + QPixmap pixmap; + pixmap.loadFromData(bytes, nullptr, flags); + return pixmap; +} + +QByteArray toQByteArray(const QPixmap& pixmap, const char* format) +{ + QByteArray bytes; + QBuffer buffer(&bytes); + buffer.open(QIODevice::WriteOnly); + pixmap.save(&buffer, format); + return bytes; +} + } // namespace QtGuiUtils } // namespace Mayo diff --git a/src/app/qtgui_utils.h b/src/app/qtgui_utils.h index 0a418e4b..810629ea 100644 --- a/src/app/qtgui_utils.h +++ b/src/app/qtgui_utils.h @@ -40,6 +40,13 @@ Quantity_Color toPreferredColorSpace(const QColor& c); // Converts (OCCT)Image_Pixmap -> QPixmap QPixmap toQPixmap(const Image_PixMap& pixmap); +// Loads QPixmap from a QByteArray object +// The loader probes the data in 'bytes' for a header to guess the file format +QPixmap toQPixmap(const QByteArray& bytes, Qt::ImageConversionFlags flags = Qt::AutoColor); + +// Saves QPixmap into a QByteArray object +QByteArray toQByteArray(const QPixmap& pixmap, const char* format = "PNG"); + // Returns linear interpolated color between 'a' and 'b' at parameter 't' QColor lerp(const QColor& a, const QColor& b, double t); diff --git a/src/app/recent_files.cpp b/src/app/recent_files.cpp index 661964e4..e64deb65 100644 --- a/src/app/recent_files.cpp +++ b/src/app/recent_files.cpp @@ -7,52 +7,13 @@ #include "recent_files.h" #include "../base/meta_enum.h" -#include "../graphics/graphics_utils.h" -#include "../gui/gui_document.h" -#include "../io_image/io_image.h" -#include "filepath_conv.h" -#include "qstring_conv.h" -#include "qtgui_utils.h" -#include "theme.h" +#include "../qtcommon/filepath_conv.h" #include #include namespace Mayo { -bool RecentFile::recordThumbnail(GuiDocument* guiDoc, QSize size) -{ - if (!guiDoc) - return false; - - if (!filepathEquivalent(this->filepath, guiDoc->document()->filePath())) { - qDebug() << fmt::format("Filepath mismatch with GUI document\n" - " Function: {}\n Filepath: {}\n Document: {}", - Q_FUNC_INFO, this->filepath.u8string(), guiDoc->document()->filePath().u8string()) - .c_str(); - return false; - } - - if (this->thumbnailTimestamp == RecentFile::timestampLastModified(this->filepath)) - return true; - - IO::ImageWriter::Parameters params; - params.width = size.width(); - params.height = size.height(); - params.backgroundColor = QtGuiUtils::toPreferredColorSpace(mayoTheme()->color(Theme::Color::Palette_Window)); - Handle_Image_AlienPixMap pixmap = IO::ImageWriter::createImage(guiDoc, params); - if (!pixmap) { - qDebug() << "Empty pixmap returned by IO::ImageWriter::createImage()"; - return false; - } - - GraphicsUtils::ImagePixmap_flipY(*pixmap); - Image_PixMap::SwapRgbaBgra(*pixmap); - this->thumbnail = QtGuiUtils::toQPixmap(*pixmap); - this->thumbnailTimestamp = RecentFile::timestampLastModified(this->filepath); - return true; -} - bool RecentFile::isThumbnailOutOfSync() const { return this->thumbnailTimestamp != RecentFile::timestampLastModified(this->filepath); @@ -74,62 +35,21 @@ int64_t RecentFile::timestampLastModified(const FilePath& fp) bool operator==(const RecentFile& lhs, const RecentFile& rhs) { - return lhs.filepath == rhs.filepath - && lhs.thumbnail.cacheKey() == rhs.thumbnail.cacheKey() - && lhs.thumbnailTimestamp == rhs.thumbnailTimestamp; -} - -QDataStream& operator<<(QDataStream& stream, const RecentFile& recentFile) -{ - stream << filepathTo(recentFile.filepath); - stream << recentFile.thumbnail; - stream << qint64(recentFile.thumbnailTimestamp); - return stream; -} - -QDataStream& operator>>(QDataStream& stream, RecentFile& recentFile) -{ - QString strFilepath; - stream >> strFilepath; - recentFile.filepath = filepathFrom(strFilepath); - stream >> recentFile.thumbnail; - // Read thumbnail timestamp - // Warning: qint64 and int64_t may not be the exact same type(eg __int64 and longlong with Windows/MSVC) - qint64 timestamp; - stream >> timestamp; - recentFile.thumbnailTimestamp = timestamp; - return stream; -} - -QDataStream& operator<<(QDataStream& stream, const RecentFiles& recentFiles) -{ - stream << uint32_t(recentFiles.size()); - for (const RecentFile& recent : recentFiles) - stream << recent; - - return stream; -} - -QDataStream& operator>>(QDataStream& stream, RecentFiles& recentFiles) -{ - uint32_t count = 0; - stream >> count; - recentFiles.clear(); - for (uint32_t i = 0; i < count; ++i) { - if (stream.status() != QDataStream::Ok) { - qDebug() << fmt::format("QDataStream error\n Function: {}\n Status: {}", - Q_FUNC_INFO, MetaEnum::name(stream.status())) - .c_str(); - break; // Stream extraction error, abort - } + if (lhs.filepath != rhs.filepath) + return false; - RecentFile recent; - stream >> recent; - if (!recent.filepath.empty() && recent.thumbnailTimestamp != 0) - recentFiles.push_back(std::move(recent)); + if (lhs.thumbnail.imageCacheKey >= 0 && rhs.thumbnail.imageCacheKey >= 0) { + if (lhs.thumbnail.imageCacheKey != rhs.thumbnail.imageCacheKey) + return false; + } + else if (lhs.thumbnail.imageData != rhs.thumbnail.imageData) { + return false; } - return stream; + if (lhs.thumbnailTimestamp != rhs.thumbnailTimestamp) + return false; + + return true; } template<> const char PropertyRecentFiles::TypeName[] = "Mayo::PropertyRecentFiles"; diff --git a/src/app/recent_files.h b/src/app/recent_files.h index 706f1a1c..13f87e27 100644 --- a/src/app/recent_files.h +++ b/src/app/recent_files.h @@ -9,20 +9,24 @@ #include "../base/filepath.h" #include "../base/property_builtins.h" -#include +#include + #include -class QDataStream; namespace Mayo { class GuiDocument; +struct Thumbnail { + QByteArray imageData; // PNG + int64_t imageCacheKey = -1; +}; + // Provides information about a "recently" opened file struct RecentFile { FilePath filepath; - QPixmap thumbnail; + Thumbnail thumbnail; int64_t thumbnailTimestamp = 0; - bool recordThumbnail(GuiDocument* guiDoc, QSize size); bool isThumbnailOutOfSync() const; static int64_t timestampLastModified(const FilePath& fp); }; @@ -36,16 +40,4 @@ using PropertyRecentFiles = GenericProperty; bool operator==(const RecentFile& lhs, const RecentFile& rhs); -// Writes a RecentFile object to QDataStream -QDataStream& operator<<(QDataStream& stream, const RecentFile& recentFile); - -// Writes array of RecentFile objects to QDataStream -QDataStream& operator<<(QDataStream& stream, const RecentFiles& recentFiles); - -// Extracts a RecentFile object from QDataStream -QDataStream& operator>>(QDataStream& stream, RecentFile& recentFile); - -// Extracts array of RecentFile objects from QDataStream -QDataStream& operator>>(QDataStream& stream, RecentFiles& recentFiles); - } // namespace Mayo diff --git a/src/app/view3d_navigation_style.h b/src/app/view3d_navigation_style.h new file mode 100644 index 00000000..5c3d4770 --- /dev/null +++ b/src/app/view3d_navigation_style.h @@ -0,0 +1,15 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +namespace Mayo { + +enum class View3dNavigationStyle { + Mayo, Catia, SolidWorks, Unigraphics, ProEngineer +}; + +} // namespace Mayo diff --git a/src/app/widget_gui_document.cpp b/src/app/widget_gui_document.cpp index 5dde3114..71e56a34 100644 --- a/src/app/widget_gui_document.cpp +++ b/src/app/widget_gui_document.cpp @@ -11,6 +11,7 @@ #include "../graphics/graphics_utils.h" #include "../gui/gui_document.h" #include "../gui/v3d_view_camera_animation.h" +#include "../qtbackend/qt_animation_backend.h" #include "button_flat.h" #include "theme.h" #include "widget_clip_planes.h" @@ -22,8 +23,6 @@ #include "qtwidgets_utils.h" #include -#include -#include #include #include #include @@ -36,58 +35,6 @@ namespace Mayo { namespace { -// Provides implementation of IAnimationBackend based on QAbstractAnimation -class QtAnimationBackend : public IAnimationBackend { -public: - QtAnimationBackend(QEasingCurve::Type easingType = QEasingCurve::Linear) - : m_easingCurve(easingType) - { - } - - void setDuration(QuantityTime t) override { - m_impl.m_duration_ms = UnitSystem::milliseconds(t); - } - - bool isRunning() const override { - return m_impl.state() == QAbstractAnimation::Running; - } - - void start() override { - m_impl.start(QAbstractAnimation::KeepWhenStopped); - } - - void stop() override { - m_impl.stop(); - } - - double valueForProgress(double p) const override { - return m_easingCurve.valueForProgress(p); - } - - void setTimerCallback(std::function fn) override { - m_impl.m_callback = std::move(fn); - } - -private: - class AnimationImpl : public QAbstractAnimation { - public: - double m_duration_ms = 1000.; - std::function m_callback; - - int duration() const override { - return static_cast(m_duration_ms); - } - - protected: - void updateCurrentTime(int currentTime) override { - m_callback(currentTime * Quantity_Millisecond); - } - }; - - AnimationImpl m_impl; - QEasingCurve m_easingCurve; -}; - // Provides an overlay widget to be used within 3D view class PanelView3d : public QWidget { public: diff --git a/src/app/widget_home_files.cpp b/src/app/widget_home_files.cpp index 52b8e4a6..1dd05adb 100644 --- a/src/app/widget_home_files.cpp +++ b/src/app/widget_home_files.cpp @@ -10,9 +10,10 @@ #include "../base/settings.h" #include "../gui/gui_application.h" #include "../gui/gui_document.h" +#include "../qtcommon/filepath_conv.h" #include "app_module.h" -#include "filepath_conv.h" #include "qstring_utils.h" +#include "qtgui_utils.h" #include "theme.h" #include @@ -46,7 +47,8 @@ class HomeFilesModel : public ListHelper::Model { HomeFileItem item; item.name = WidgetHomeFiles::tr("New Document"); item.description = WidgetHomeFiles::tr( - "\n\nCreate and add an empty document where you can import files"); + "\n\nCreate and add an empty document where you can import files" + ); item.imageUrl = ImageId_NewDocument; item.type = HomeFileItem::Type::New; item.textWrapMode = QTextOption::WordWrap; @@ -57,7 +59,8 @@ class HomeFilesModel : public ListHelper::Model { HomeFileItem item; item.name = WidgetHomeFiles::tr("Open Document(s)"); item.description = WidgetHomeFiles::tr( - "\n\nSelect files to load and open as distinct documents"); + "\n\nSelect files to load and open as distinct documents" + ); item.imageUrl = ImageId_OpenDocuments; item.type = HomeFileItem::Type::Open; item.textWrapMode = QTextOption::WordWrap; @@ -85,11 +88,14 @@ class HomeFilesModel : public ListHelper::Model { } else { const RecentFile* recentFile = AppModule::get()->findRecentFile(filepathFrom(url)); - pixmap = recentFile ? recentFile->thumbnail : QPixmap(); + if (recentFile) + pixmap = QtGuiUtils::toQPixmap(recentFile->thumbnail.imageData); + if (pixmap.isNull()) { const QIcon icon = m_fileIconProvider.icon(QFileInfo(url)); pixmap = fnPixmap(icon, 64, 64); cachePixmap = false; + } } diff --git a/src/app/widget_main_control.cpp b/src/app/widget_main_control.cpp index 3240e3ae..42704da2 100644 --- a/src/app/widget_main_control.cpp +++ b/src/app/widget_main_control.cpp @@ -10,16 +10,16 @@ #include "../base/application.h" #include "../graphics/graphics_utils.h" #include "../gui/gui_application.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include "app_module.h" #include "commands_api.h" #include "commands_file.h" #include "commands_window.h" #include "document_property_group.h" -#include "filepath_conv.h" #include "gui_document_list_model.h" #include "item_view_buttons.h" -#include "qstring_conv.h" #include "theme.h" #include "widget_file_system.h" #include "widget_gui_document.h" diff --git a/src/app/widget_main_home.cpp b/src/app/widget_main_home.cpp index 5b6a45eb..0c1dd4ad 100644 --- a/src/app/widget_main_home.cpp +++ b/src/app/widget_main_home.cpp @@ -7,8 +7,8 @@ #include "widget_main_home.h" #include "ui_widget_main_home.h" +#include "../qtcommon/filepath_conv.h" #include "commands_file.h" -#include "filepath_conv.h" namespace Mayo { diff --git a/src/app/widget_measure.cpp b/src/app/widget_measure.cpp index 237bb184..570af0e4 100644 --- a/src/app/widget_measure.cpp +++ b/src/app/widget_measure.cpp @@ -6,13 +6,13 @@ #include "widget_measure.h" #include "app_module.h" -#include "qstring_conv.h" #include "theme.h" #include "ui_widget_measure.h" #include "../base/unit_system.h" #include "../gui/gui_document.h" #include "../measure/measure_tool_brep.h" +#include "../qtcommon/qstring_conv.h" #include #include diff --git a/src/app/widget_model_tree.cpp b/src/app/widget_model_tree.cpp index a2939bbe..0ee000c6 100644 --- a/src/app/widget_model_tree.cpp +++ b/src/app/widget_model_tree.cpp @@ -11,8 +11,8 @@ #include "../base/document.h" #include "../base/settings.h" #include "../gui/gui_application.h" +#include "../qtcommon/qtcore_utils.h" #include "item_view_buttons.h" -#include "qtcore_utils.h" #include "theme.h" #include "widget_model_tree_builder.h" diff --git a/src/app/widget_model_tree_builder.cpp b/src/app/widget_model_tree_builder.cpp index 1f840ac1..7d2b000a 100644 --- a/src/app/widget_model_tree_builder.cpp +++ b/src/app/widget_model_tree_builder.cpp @@ -8,9 +8,8 @@ #include "../base/document.h" #include "../base/caf_utils.h" -#include "../base/filepath.h" -#include "filepath_conv.h" -#include "qstring_conv.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/qstring_conv.h" #include "widget_model_tree.h" #include "theme.h" diff --git a/src/app/widget_model_tree_builder_xde.cpp b/src/app/widget_model_tree_builder_xde.cpp index 01e5277c..0320a763 100644 --- a/src/app/widget_model_tree_builder_xde.cpp +++ b/src/app/widget_model_tree_builder_xde.cpp @@ -8,9 +8,9 @@ #include "../base/xcaf.h" #include "../graphics/graphics_shape_object_driver.h" #include "../gui/gui_application.h" +#include "../qtcommon/qstring_conv.h" +#include "../qtcommon/qtcore_utils.h" #include "app_module.h" -#include "qtcore_utils.h" -#include "qstring_conv.h" #include "theme.h" #include "widget_model_tree.h" diff --git a/src/app/widget_occ_view_controller.cpp b/src/app/widget_occ_view_controller.cpp index c3edeaaf..eb94aa9e 100644 --- a/src/app/widget_occ_view_controller.cpp +++ b/src/app/widget_occ_view_controller.cpp @@ -102,7 +102,7 @@ WidgetOccViewController::WidgetOccViewController(IWidgetOccView* occView) : QObject(occView->widget()), V3dViewController(occView->v3dView()), m_occView(occView), - m_navigStyle(NavigationStyle::Catia), + m_navigStyle(View3dNavigationStyle::Catia), m_actionMatcher(createActionMatcher(m_navigStyle, &m_inputSequence)) { m_occView->widget()->installEventFilter(this); @@ -130,7 +130,7 @@ bool WidgetOccViewController::eventFilter(QObject* watched, QEvent* event) return false; } -void WidgetOccViewController::setNavigationStyle(NavigationStyle style) +void WidgetOccViewController::setNavigationStyle(View3dNavigationStyle style) { m_navigStyle = style; m_inputSequence.clear(); @@ -448,14 +448,14 @@ class WidgetOccViewController::ProEngineer_ActionMatcher : public ActionMatcher }; std::unique_ptr -WidgetOccViewController::createActionMatcher(NavigationStyle style, const InputSequence* seq) +WidgetOccViewController::createActionMatcher(View3dNavigationStyle style, const InputSequence* seq) { switch(style) { - case NavigationStyle::Mayo: return std::make_unique(seq); - case NavigationStyle::Catia: return std::make_unique(seq); - case NavigationStyle::SolidWorks: return std::make_unique(seq); - case NavigationStyle::Unigraphics: return std::make_unique(seq); - case NavigationStyle::ProEngineer: return std::make_unique(seq); + case View3dNavigationStyle::Mayo: return std::make_unique(seq); + case View3dNavigationStyle::Catia: return std::make_unique(seq); + case View3dNavigationStyle::SolidWorks: return std::make_unique(seq); + case View3dNavigationStyle::Unigraphics: return std::make_unique(seq); + case View3dNavigationStyle::ProEngineer: return std::make_unique(seq); } return {}; } diff --git a/src/app/widget_occ_view_controller.h b/src/app/widget_occ_view_controller.h index 7ce388d1..a7311335 100644 --- a/src/app/widget_occ_view_controller.h +++ b/src/app/widget_occ_view_controller.h @@ -6,8 +6,9 @@ #pragma once -#include "../gui/v3d_view_controller.h" #include "../base/span.h" +#include "../gui/v3d_view_controller.h" +#include "view3d_navigation_style.h" #include #include @@ -31,10 +32,7 @@ class WidgetOccViewController : public QObject, public V3dViewController { bool eventFilter(QObject* watched, QEvent* event) override; - enum class NavigationStyle { - Mayo, Catia, SolidWorks, Unigraphics, ProEngineer - }; - void setNavigationStyle(NavigationStyle style); + void setNavigationStyle(View3dNavigationStyle style); protected: void redrawView() override; @@ -110,7 +108,7 @@ class WidgetOccViewController : public QObject, public V3dViewController { }; // Fabrication to create corresponding ActionMatcher from navigation style - static std::unique_ptr createActionMatcher(NavigationStyle style, const InputSequence* seq); + static std::unique_ptr createActionMatcher(View3dNavigationStyle style, const InputSequence* seq); class Mayo_ActionMatcher; class Catia_ActionMatcher; class SolidWorks_ActionMatcher; @@ -121,7 +119,7 @@ class WidgetOccViewController : public QObject, public V3dViewController { IWidgetOccView* m_occView = nullptr; Position m_prevPos; - NavigationStyle m_navigStyle = NavigationStyle::Mayo; + View3dNavigationStyle m_navigStyle = View3dNavigationStyle::Mayo; InputSequence m_inputSequence; std::unique_ptr m_actionMatcher; }; diff --git a/src/app/widget_occ_view_impl.cpp b/src/app/widget_occ_view_impl.cpp index d85711ce..682d7925 100644 --- a/src/app/widget_occ_view_impl.cpp +++ b/src/app/widget_occ_view_impl.cpp @@ -4,7 +4,7 @@ ** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt ****************************************************************************/ -#include +#include "../graphics/graphics_utils.h" #include #include @@ -19,19 +19,6 @@ namespace Mayo { -namespace { - -Handle_Aspect_DisplayConnection createDisplayConnection() -{ -#if (!defined(Q_OS_WIN) && (!defined(Q_OS_MAC) || defined(MACOSX_USE_GLX))) - return new Aspect_DisplayConnection(std::getenv("DISPLAY")); -#else - return new Aspect_DisplayConnection; -#endif -} - -} // namespace - #if OCC_VERSION_HEX >= 0x070600 namespace { @@ -87,20 +74,13 @@ void QOpenGLWidgetOccView_createOpenGlContext(std::functionChangeOptions().buffersNoSwap = true; // Don't write into alpha channel gfxDriver->ChangeOptions().buffersOpaqueAlpha = true; // Offscreen FBOs should be always used gfxDriver->ChangeOptions().useSystemBuffer = false; -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - Message::SendWarning( - "Warning! Qt 5.10+ is required for sRGB setup.\n" - "Colors in 3D Viewer might look incorrect (Qt " QT_VERSION_STR " is used).\n" - ); - gfxDriver->ChangeOptions().sRGBDisable = true; -#endif return gfxDriver; } @@ -139,7 +119,7 @@ Graphic3d_Vec2i QOpenGLWidgetOccView_getDefaultframeBufferViewportSize(const Han Handle_Graphic3d_GraphicDriver QWidgetOccView_createCompatibleGraphicsDriver() { - return new OpenGl_GraphicDriver(createDisplayConnection()); + return new OpenGl_GraphicDriver(GraphicsUtils::AspectDisplayConnection_create()); } } // namespace Mayo diff --git a/src/app/widget_properties_editor.cpp b/src/app/widget_properties_editor.cpp index 72fe5038..3c43c921 100644 --- a/src/app/widget_properties_editor.cpp +++ b/src/app/widget_properties_editor.cpp @@ -6,8 +6,8 @@ #include "widget_properties_editor.h" #include "qmeta_property.h" -#include "qstring_conv.h" #include "ui_widget_properties_editor.h" +#include "../qtcommon/qstring_conv.h" #include diff --git a/src/app/cli_export.cpp b/src/cli/cli_export.cpp similarity index 99% rename from src/app/cli_export.cpp rename to src/cli/cli_export.cpp index f5d7d428..989267cc 100644 --- a/src/app/cli_export.cpp +++ b/src/cli/cli_export.cpp @@ -6,13 +6,13 @@ #include "cli_export.h" -#include "app_module.h" #include "console.h" -#include "qstring_conv.h" +#include "../app/app_module.h" #include "../base/application.h" #include "../base/io_system.h" #include "../base/messenger.h" #include "../base/task_manager.h" +#include "../qtcommon/qstring_conv.h" #include diff --git a/src/app/cli_export.h b/src/cli/cli_export.h similarity index 100% rename from src/app/cli_export.h rename to src/cli/cli_export.h diff --git a/src/app/console.cpp b/src/cli/console.cpp similarity index 83% rename from src/app/console.cpp rename to src/cli/console.cpp index 183cac75..3e0844bb 100644 --- a/src/app/console.cpp +++ b/src/cli/console.cpp @@ -6,8 +6,9 @@ #include "console.h" -#include -#ifdef Q_OS_WIN +#include "../base/global.h" + +#ifdef MAYO_OS_WINDOWS # include # include #else @@ -16,15 +17,12 @@ # include #endif -#include "../base/global.h" -#include "qstring_conv.h" - namespace Mayo { void consoleSetTextColor(ConsoleColor color) { constexpr bool isBrightText = true; -#ifdef Q_OS_WIN +#ifdef MAYO_OS_WINDOWS auto fnColorFlags = [](ConsoleColor color) { switch (color) { case ConsoleColor::Black: return 0; @@ -48,7 +46,7 @@ void consoleSetTextColor(ConsoleColor color) const WORD flags = fnColorFlags(color) | brightFlag; SetConsoleTextAttribute(hStdout, flags); } -#elif defined(__EMSCRIPTEN__) +#elif defined(MAYO_OS_WASM) // Terminal capabilities are undefined on this platform. // std::cout could be redirected to HTML page, into terminal or somewhere else. MAYO_UNUSED(color); @@ -77,7 +75,7 @@ void consoleCursorMoveUp(int lines) if (lines == 0) return; -#ifdef Q_OS_WIN +#ifdef MAYO_OS_WINDOWS auto hStdout = GetStdHandle(STD_OUTPUT_HANDLE); if (hStdout) { CONSOLE_SCREEN_BUFFER_INFO buffInfo; @@ -94,7 +92,7 @@ void consoleCursorMoveUp(int lines) void consoleCursorShow(bool on) { -#ifdef Q_OS_WIN +#ifdef MAYO_OS_WINDOWS HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); if (hStdout) { CONSOLE_CURSOR_INFO cursorInfo; @@ -109,7 +107,7 @@ void consoleCursorShow(bool on) std::pair consoleSize() { -#ifdef Q_OS_WIN +#ifdef MAYO_OS_WINDOWS CONSOLE_SCREEN_BUFFER_INFO buffInfo; int cols, rows; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &buffInfo); @@ -129,36 +127,11 @@ int consoleWidth() { void consoleSendEnterKey() { -#ifdef Q_OS_WIN +#ifdef MAYO_OS_WINDOWS HWND consoleWnd = GetConsoleWindow(); if (IsWindow(consoleWnd)) PostMessage(consoleWnd, WM_KEYUP, VK_RETURN, 0); #endif } -std::string consoleToPrintable(const QString& str) -{ -#ifdef Q_OS_WIN - const auto codepage = GetConsoleOutputCP(); - const wchar_t* source = reinterpret_cast(str.utf16()); - const int dstSize = WideCharToMultiByte(codepage, 0, source, -1, nullptr, 0, nullptr, nullptr); - std::string dst; - dst.resize(dstSize + 1); - WideCharToMultiByte(codepage, 0, source, -1, dst.data(), dstSize, nullptr, nullptr); - dst.back() = '\0'; - return dst; -#else - return str.toStdString(); // utf8 -#endif -} - -std::string consoleToPrintable(std::string_view str) -{ -#ifdef Q_OS_WIN - return consoleToPrintable(to_QString(str)); -#else - return std::string(str); // utf8 -#endif -} - } // namespace Mayo diff --git a/src/app/console.h b/src/cli/console.h similarity index 82% rename from src/app/console.h rename to src/cli/console.h index f474ba57..19699e56 100644 --- a/src/app/console.h +++ b/src/cli/console.h @@ -6,9 +6,6 @@ #pragma once -#include -#include -#include #include namespace Mayo { @@ -36,8 +33,4 @@ int consoleWidth(); // Useful to release the command prompt on the parent console when using AttachConsole() void consoleSendEnterKey(); -// Returns 'str' converted to a "guaranteed" printable string -std::string consoleToPrintable(const QString& str); -std::string consoleToPrintable(std::string_view str/*utf8*/); - } // namespace Mayo diff --git a/src/cli/main.cpp b/src/cli/main.cpp new file mode 100644 index 00000000..725a9369 --- /dev/null +++ b/src/cli/main.cpp @@ -0,0 +1,351 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "../app/app_module.h" +#include "../base/application.h" +#include "../base/io_system.h" +#include "../base/settings.h" +#include "../graphics/graphics_mesh_object_driver.h" +#include "../graphics/graphics_point_cloud_object_driver.h" +#include "../graphics/graphics_shape_object_driver.h" +#include "../graphics/graphics_utils.h" +#include "../gui/gui_application.h" +#include "../io_assimp/io_assimp.h" +#include "../io_dxf/io_dxf.h" +#include "../io_gmio/io_gmio.h" +#include "../io_image/io_image.h" +#include "../io_occ/io_occ.h" +#include "../io_off/io_off_reader.h" +#include "../io_off/io_off_writer.h" +#include "../io_ply/io_ply_reader.h" +#include "../io_ply/io_ply_writer.h" +#include "../qtbackend/qsettings_storage.h" +#include "../qtbackend/qt_app_translator.h" +#include "../qtbackend/qt_signal_thread_helper.h" +#include "../qtcommon/filepath_conv.h" +#include "../qtcommon/log_message_handler.h" +#include "../qtcommon/qstring_conv.h" +#include "cli_export.h" +#include "console.h" +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace Mayo { + +// Declared in graphics/graphics_create_driver.cpp +void setFunctionCreateGraphicsDriver(std::function fn); + +// Provides an i18n context for the current file(main.cpp) +class Main { + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::Main) + Q_DECLARE_TR_FUNCTIONS(Mayo::Main) +}; + +namespace { + +// Stores arguments(options) passed at command line +struct CommandLineArguments { + FilePath filepathSettings; + FilePath filepathLog; + bool includeDebugLogs = true; + std::vector listFilepathToExport; + std::vector listFilepathToOpen; + bool cliProgressReport = true; + bool showSystemInformation = false; +}; + +} // namespace + +// Parses command line and process Qt builtin options(basically --version and --help) +static CommandLineArguments processCommandLine() +{ + CommandLineArguments args; + + // Configure command-line parser + QCommandLineParser cmdParser; + cmdParser.setApplicationDescription( + Main::tr("Mayo the opensource 3D CAD viewer and converter") + ); + cmdParser.addHelpOption(); + cmdParser.addVersionOption(); + + const QCommandLineOption cmdFileSettings( + QStringList{ "s", "settings" }, + Main::tr("Settings file(INI format) to load at startup"), + Main::tr("filepath") + ); + cmdParser.addOption(cmdFileSettings); + + const QCommandLineOption cmdFileToExport( + QStringList{ "e", "export" }, + Main::tr("Export opened files into an output file, can be repeated for different " + "formats(eg. -e file.stp -e file.igs...)"), + Main::tr("filepath") + ); + cmdParser.addOption(cmdFileToExport); + + const QCommandLineOption cmdFileLog( + QStringList{ "log-file" }, + Main::tr("Writes log messages into output file"), + Main::tr("filepath") + ); + cmdParser.addOption(cmdFileLog); + + const QCommandLineOption cmdDebugLogs( + QStringList{ "debug-logs" }, + Main::tr("Don't filter out debug log messages in release build") + ); + cmdParser.addOption(cmdDebugLogs); + + const QCommandLineOption cmdCliNoProgress( + QStringList{ "no-progress" }, + Main::tr("Disable progress reporting in console output(CLI-mode only)") + ); + cmdParser.addOption(cmdCliNoProgress); + + const QCommandLineOption cmdSysInfo( + QStringList{ "system-info" }, + Main::tr("Show detailed system information and quit") + ); + cmdParser.addOption(cmdSysInfo); + + cmdParser.addPositionalArgument( + Main::tr("files"), + Main::tr("Files to open at startup, optionally"), + Main::tr("[files...]") + ); + + cmdParser.process(QCoreApplication::arguments()); + + // Retrieve arguments + if (cmdParser.isSet(cmdFileSettings)) + args.filepathSettings = filepathFrom(cmdParser.value(cmdFileSettings)); + + if (cmdParser.isSet(cmdFileLog)) + args.filepathLog = filepathFrom(cmdParser.value(cmdFileLog)); + + if (cmdParser.isSet(cmdFileToExport)) { + for (const QString& strFilepath : cmdParser.values(cmdFileToExport)) + args.listFilepathToExport.push_back(filepathFrom(strFilepath)); + } + + for (const QString& posArg : cmdParser.positionalArguments()) + args.listFilepathToOpen.push_back(filepathFrom(posArg)); + +#ifdef NDEBUG + // By default this will exclude debug logs in release build + args.includeDebugLogs = cmdParser.isSet(cmdDebugLogs); +#endif + args.cliProgressReport = !cmdParser.isSet(cmdCliNoProgress); + args.showSystemInformation = cmdParser.isSet(cmdSysInfo); + + return args; +} + +// Set OpenCascade environment variables defined in a settings file(INI format) +static void initOpenCascadeEnvironment(const FilePath& settingsFilepath) +{ + const QString strSettingsFilepath = filepathTo(settingsFilepath); + if (!filepathExists(settingsFilepath) /* TODO Check readable */) { + qDebug().noquote() << Main::tr("OpenCascade settings file doesn't exist or is not readable [path=%1]") + .arg(strSettingsFilepath); + return; + } + + const QSettings occSettings(strSettingsFilepath, QSettings::IniFormat); + if (occSettings.status() != QSettings::NoError) { + qDebug().noquote() << Main::tr("OpenCascade settings file could not be loaded with QSettings [path=%1]") + .arg(strSettingsFilepath); + return; + } + + // Process options + for (const char* varName : Application::envOpenCascadeOptions()) { + const QLatin1String qVarName(varName); + if (occSettings.contains(qVarName)) { + const QString strValue = occSettings.value(qVarName).toString(); + qputenv(varName, strValue.toUtf8()); + qDebug().noquote() << QString("%1 = %2").arg(qVarName).arg(strValue); + } + } + + // Process paths + for (const char* varName : Application::envOpenCascadePaths()) { + const QLatin1String qVarName(varName); + if (occSettings.contains(qVarName)) { + QString strPath = occSettings.value(qVarName).toString(); + if (QFileInfo(strPath).isRelative()) + strPath = QCoreApplication::applicationDirPath() + QDir::separator() + strPath; + + strPath = QDir::toNativeSeparators(strPath); + qputenv(varName, strPath.toUtf8()); + qDebug().noquote() << QString("%1 = %2").arg(qVarName).arg(strPath); + } + } +} + +// Initializes "GUI" objects +static void initGui(GuiApplication* guiApp) +{ + if (!guiApp) + return; + + setFunctionCreateGraphicsDriver([]() -> Handle_Graphic3d_GraphicDriver { + return new OpenGl_GraphicDriver(GraphicsUtils::AspectDisplayConnection_create()); + }); + guiApp->addGraphicsObjectDriver(std::make_unique()); + guiApp->addGraphicsObjectDriver(std::make_unique()); + guiApp->addGraphicsObjectDriver(std::make_unique()); +} + +bool cliExcludingSettingPredicate(const Property& prop) +{ + return AppModule::excludeSettingPredicate(prop) + || prop.dynTypeName() == PropertyRecentFiles::TypeName; +} + +// Initializes and runs Mayo application +static int runApp(QCoreApplication* qtApp) +{ + const CommandLineArguments args = processCommandLine(); + + // Helper function: print critical message and exit application with code failure + auto fnCriticalExit = [](const QString& msg) { + qCritical().noquote() << msg; + std::exit(EXIT_FAILURE); + }; + + // Helper function: load application settings from INI file(if provided) otherwise use the + // application regular storage(eg registry on Windows) + auto fnLoadAppSettings = [&](Settings* appSettings) { + if (args.filepathSettings.empty()) { + appSettings->load(); + } + else { + const QString strFilepathSettings = filepathTo(args.filepathSettings); + if (!filepathIsRegularFile(args.filepathSettings)) + fnCriticalExit(Main::tr("Failed to load application settings file [path=%1]").arg(strFilepathSettings)); + + QSettingsStorage fileSettings(strFilepathSettings, QSettings::IniFormat); + appSettings->loadFrom(fileSettings, &cliExcludingSettingPredicate); + } + }; + + // Signals + setGlobalSignalThreadHelper(std::make_unique()); + + // Message logging + LogMessageHandler::instance().enableDebugLogs(args.includeDebugLogs); + LogMessageHandler::instance().setOutputFilePath(args.filepathLog); + + // Initialize AppModule + auto appModule = AppModule::get(); + appModule->settings()->setStorage(std::make_unique()); + { + // Load translation files + auto fnLoadQmFile = [=](const QString& qmFilePath) { + auto translator = new QTranslator(qtApp); + if (translator->load(qmFilePath)) + qtApp->installTranslator(translator); + else + qWarning() << Main::tr("Failed to load translation file [path=%1]").arg(qmFilePath); + }; + const QString appLangCode = appModule->languageCode(); + fnLoadQmFile(QString(":/i18n/mayo_%1.qm").arg(appLangCode)); + fnLoadQmFile(QString(":/i18n/qtbase_%1.qm").arg(appLangCode)); + } + + // Initialize Base application + auto app = Application::instance().get(); + app->addTranslator(&qtAppTranslate); // Set Qt i18n backend + initOpenCascadeEnvironment("opencascade.conf"); + + // Initialize Gui application + auto guiApp = new GuiApplication(app); + initGui(guiApp); + + // Register I/O objects + IO::System* ioSystem = appModule->ioSystem(); + ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryReader(IO::AssimpFactoryReader::create()); + ioSystem->addFactoryWriter(std::make_unique()); + ioSystem->addFactoryWriter(std::make_unique()); + ioSystem->addFactoryWriter(std::make_unique()); + ioSystem->addFactoryWriter(IO::GmioFactoryWriter::create()); + ioSystem->addFactoryWriter(std::make_unique(guiApp)); + IO::addPredefinedFormatProbes(ioSystem); + appModule->properties()->IO_bindParameters(ioSystem); + appModule->properties()->retranslate(); + +#if 0 + // Register library infos + CommandSystemInformation::addLibraryInfo( + IO::AssimpLib::strName(), IO::AssimpLib::strVersion(), IO::AssimpLib::strVersionDetails() + ); + CommandSystemInformation::addLibraryInfo( + IO::GmioLib::strName(), IO::GmioLib::strVersion(), IO::GmioLib::strVersionDetails() + ); + + // Process CLI + if (args.showSystemInformation) { + CommandSystemInformation cmdSysInfo(nullptr); + cmdSysInfo.execute(); + return qtApp->exec(); + } +#endif + + if (!args.listFilepathToExport.empty()) { + if (args.listFilepathToOpen.empty()) + fnCriticalExit(Main::tr("No input files -> nothing to export")); + + guiApp->setAutomaticDocumentMapping(false); // GuiDocument objects aren't needed + appModule->settings()->resetAll(); + fnLoadAppSettings(appModule->settings()); + QTimer::singleShot(0, qtApp, [=]{ + CliExportArgs cliArgs; + cliArgs.progressReport = args.cliProgressReport; + cliArgs.filesToOpen = args.listFilepathToOpen; + cliArgs.filesToExport = args.listFilepathToExport; + cli_asyncExportDocuments(app, cliArgs, [=](int retcode) { qtApp->exit(retcode); }); + }); + return qtApp->exec(); + } + + return 0; +} + +} // namespace Mayo + +int main(int argc, char* argv[]) +{ + qInstallMessageHandler(&Mayo::LogMessageHandler::qtHandler); + + // Configure and create Qt application object + QCoreApplication::setOrganizationName("Fougue Ltd"); + QCoreApplication::setOrganizationDomain("www.fougue.pro"); + QCoreApplication::setApplicationName("MayoConv"); + QCoreApplication::setApplicationVersion(QString::fromUtf8(Mayo::strVersion)); + QCoreApplication app(argc, argv); + // TODO Read settings from Mayo application + return Mayo::runApp(&app); +} diff --git a/src/graphics/graphics_utils.cpp b/src/graphics/graphics_utils.cpp index e33b1adb..f44e89ce 100644 --- a/src/graphics/graphics_utils.cpp +++ b/src/graphics/graphics_utils.cpp @@ -6,6 +6,7 @@ #include "graphics_utils.h" #include "../base/bnd_utils.h" +#include "../base/global.h" #include "../base/math_utils.h" #include "../base/tkernel_utils.h" @@ -18,6 +19,7 @@ #include #include +#include namespace Mayo { @@ -204,6 +206,15 @@ int GraphicsUtils::AspectWindow_height(const Handle_Aspect_Window& wnd) return h; } +Handle_Aspect_DisplayConnection GraphicsUtils::AspectDisplayConnection_create() +{ +#if (!defined(MAYO_OS_WINDOWS) && (!defined(MAYO_OS_MAC) || defined(MACOSX_USE_GLX))) + return new Aspect_DisplayConnection(std::getenv("DISPLAY")); +#else + return new Aspect_DisplayConnection; +#endif +} + void GraphicsUtils::Gfx3dClipPlane_setCappingHatch( const Handle_Graphic3d_ClipPlane& plane, Aspect_HatchStyle hatch) { diff --git a/src/graphics/graphics_utils.h b/src/graphics/graphics_utils.h index 494a3ea8..b0913aff 100644 --- a/src/graphics/graphics_utils.h +++ b/src/graphics/graphics_utils.h @@ -9,6 +9,7 @@ #include "graphics_object_ptr.h" #include #include +#include #include #include #include @@ -50,6 +51,7 @@ struct GraphicsUtils { static int AspectWindow_width(const Handle_Aspect_Window& wnd); static int AspectWindow_height(const Handle_Aspect_Window& wnd); + static Handle_Aspect_DisplayConnection AspectDisplayConnection_create(); static void Gfx3dClipPlane_setCappingHatch( const Handle_Graphic3d_ClipPlane& plane, Aspect_HatchStyle hatch diff --git a/src/app/qsettings_storage.cpp b/src/qtbackend/qsettings_storage.cpp similarity index 97% rename from src/app/qsettings_storage.cpp rename to src/qtbackend/qsettings_storage.cpp index fd345f0d..69c27c4a 100644 --- a/src/app/qsettings_storage.cpp +++ b/src/qtbackend/qsettings_storage.cpp @@ -6,8 +6,8 @@ #include "qsettings_storage.h" -#include "qstring_conv.h" -#include "qtcore_utils.h" +#include "../qtcommon/qstring_conv.h" +#include "../qtcommon/qtcore_utils.h" namespace Mayo { diff --git a/src/app/qsettings_storage.h b/src/qtbackend/qsettings_storage.h similarity index 100% rename from src/app/qsettings_storage.h rename to src/qtbackend/qsettings_storage.h diff --git a/src/qtbackend/qt_animation_backend.cpp b/src/qtbackend/qt_animation_backend.cpp new file mode 100644 index 00000000..5b28d938 --- /dev/null +++ b/src/qtbackend/qt_animation_backend.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "qt_animation_backend.h" + +#include "../base/unit_system.h" + +namespace Mayo { + +QtAnimationBackend::QtAnimationBackend(QEasingCurve::Type easingType) + : m_easingCurve(easingType) +{ +} + +void QtAnimationBackend::setDuration(QuantityTime t) +{ + m_impl.m_duration_ms = UnitSystem::milliseconds(t); +} + +bool QtAnimationBackend::isRunning() const +{ + return m_impl.state() == QAbstractAnimation::Running; +} + +void QtAnimationBackend::start() +{ + m_impl.start(QAbstractAnimation::KeepWhenStopped); +} + +void QtAnimationBackend::stop() +{ + m_impl.stop(); +} + +double QtAnimationBackend::valueForProgress(double p) const +{ + return m_easingCurve.valueForProgress(p); +} + +void QtAnimationBackend::setTimerCallback(std::function fn) +{ + m_impl.m_callback = std::move(fn); +} + +int QtAnimationBackend::AnimationImpl::duration() const +{ + return static_cast(m_duration_ms); +} + +void QtAnimationBackend::AnimationImpl::updateCurrentTime(int currentTime) +{ + m_callback(currentTime * Quantity_Millisecond); +} + +} // namespace Mayo diff --git a/src/qtbackend/qt_animation_backend.h b/src/qtbackend/qt_animation_backend.h new file mode 100644 index 00000000..5e874157 --- /dev/null +++ b/src/qtbackend/qt_animation_backend.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../gui/v3d_view_camera_animation.h" + +#include +#include + +namespace Mayo { + +// Provides implementation of IAnimationBackend based on QAbstractAnimation +class QtAnimationBackend : public IAnimationBackend { +public: + QtAnimationBackend(QEasingCurve::Type easingType = QEasingCurve::Linear); + + void setDuration(QuantityTime t) override; + + bool isRunning() const override; + + void start() override; + void stop() override; + + double valueForProgress(double p) const override; + + void setTimerCallback(std::function fn) override; + +private: + class AnimationImpl : public QAbstractAnimation { + public: + double m_duration_ms = 1000.; + std::function m_callback; + + int duration() const override; + + protected: + void updateCurrentTime(int currentTime) override; + }; + + AnimationImpl m_impl; + QEasingCurve m_easingCurve; +}; + +} // namespace Mayo diff --git a/src/qtbackend/qt_app_translator.cpp b/src/qtbackend/qt_app_translator.cpp new file mode 100644 index 00000000..073c160f --- /dev/null +++ b/src/qtbackend/qt_app_translator.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "qt_app_translator.h" + +#include +#include + +#include + +namespace Mayo { + +// Function called by the Application i18n system, see Application::addTranslator() +std::string_view qtAppTranslate(const TextId& text, int n) +{ + const QString qstr = QCoreApplication::translate(text.trContext.data(), text.key.data(), nullptr, n); + auto qstrHash = qHash(qstr); + static std::unordered_map mapStr; + static QReadWriteLock mapStrLock; + { + QReadLocker locker(&mapStrLock); + auto it = mapStr.find(qstrHash); + if (it != mapStr.cend()) + return it->second; + } + + QWriteLocker locker(&mapStrLock); + auto [it, ok] = mapStr.insert({ qstrHash, qstr.toStdString() }); + return ok ? it->second : std::string_view{}; +} + +} // namespace Mayo diff --git a/src/qtbackend/qt_app_translator.h b/src/qtbackend/qt_app_translator.h new file mode 100644 index 00000000..ef4ff5a3 --- /dev/null +++ b/src/qtbackend/qt_app_translator.h @@ -0,0 +1,18 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/text_id.h" + +#include + +namespace Mayo { + +// Function called by the Application i18n system, see Application::addTranslator() +std::string_view qtAppTranslate(const TextId& text, int n); + +} // namespace Mayo diff --git a/src/qtbackend/qt_signal_thread_helper.cpp b/src/qtbackend/qt_signal_thread_helper.cpp new file mode 100644 index 00000000..06cf28ec --- /dev/null +++ b/src/qtbackend/qt_signal_thread_helper.cpp @@ -0,0 +1,26 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "qt_signal_thread_helper.h" + +#include + +namespace Mayo { + +std::any QtSignalThreadHelper::getCurrentThreadContext() +{ + // Note: thread_local implies "static" + // See https://en.cppreference.com/w/cpp/language/storage_duration + thread_local QObject obj; + return &obj; +} + +void QtSignalThreadHelper::execInThread(const std::any& context, const std::function& fn) +{ + QTimer::singleShot(0, std::any_cast(context), fn); +} + +} // namespace Mayo diff --git a/src/qtbackend/qt_signal_thread_helper.h b/src/qtbackend/qt_signal_thread_helper.h new file mode 100644 index 00000000..dadbbe95 --- /dev/null +++ b/src/qtbackend/qt_signal_thread_helper.h @@ -0,0 +1,21 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/signal.h" + +namespace Mayo { + +// Provides handling of signal/slot thread mismatch with the help of Qt +// There will be a single QObject created per thread, so it can be used to enqueue slot functions +class QtSignalThreadHelper : public ISignalThreadHelper { +public: + std::any getCurrentThreadContext() override; + void execInThread(const std::any& context, const std::function& fn) override; +}; + +} // namespace Mayo diff --git a/src/app/filepath_conv.h b/src/qtcommon/filepath_conv.h similarity index 100% rename from src/app/filepath_conv.h rename to src/qtcommon/filepath_conv.h diff --git a/src/qtcommon/log_message_handler.cpp b/src/qtcommon/log_message_handler.cpp new file mode 100644 index 00000000..e34ad0e6 --- /dev/null +++ b/src/qtcommon/log_message_handler.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "log_message_handler.h" +#include "qstring_conv.h" + +#include + +namespace Mayo { + +LogMessageHandler& LogMessageHandler::instance() +{ + static LogMessageHandler object; + return object; +} + +void LogMessageHandler::enableDebugLogs(bool on) +{ + m_enableDebugLogs = on; +} + +void LogMessageHandler::setOutputFilePath(const FilePath& fp) +{ + m_outputFilePath = fp; + if (!fp.empty()) + m_outputFile.open(fp, std::ios::out | std::ios::app); + else + m_outputFile.close(); +} + +std::ostream& LogMessageHandler::outputStream(QtMsgType type) +{ + if (!m_outputFilePath.empty() && m_outputFile.is_open()) + return m_outputFile; + + if (type == QtDebugMsg || type == QtInfoMsg) + return std::cout; + + return std::cerr; +} + +void LogMessageHandler::qtHandler(QtMsgType type, const QMessageLogContext& /*context*/, const QString& msg) +{ + const std::string localMsg = consoleToPrintable(msg); + std::ostream& outs = LogMessageHandler::instance().outputStream(type); + switch (type) { + case QtDebugMsg: + if (LogMessageHandler::instance().m_enableDebugLogs) { + outs << "DEBUG: " << localMsg << std::endl; + } + break; + case QtInfoMsg: + outs << "INFO: " << localMsg << std::endl; + break; + case QtWarningMsg: + outs << "WARNING: " << localMsg << std::endl; + break; + case QtCriticalMsg: + outs << "CRITICAL: " << localMsg << std::endl; + break; + case QtFatalMsg: + outs << "FATAL: " << localMsg << std::endl; + break; + } +} + +} // namespace Mayo diff --git a/src/qtcommon/log_message_handler.h b/src/qtcommon/log_message_handler.h new file mode 100644 index 00000000..d23bc8c5 --- /dev/null +++ b/src/qtcommon/log_message_handler.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/filepath.h" + +#include +#include + +#include + +namespace Mayo { + +// Provides customization of Qt message handler +class LogMessageHandler { +public: + static LogMessageHandler& instance(); + + void enableDebugLogs(bool on); + void setOutputFilePath(const FilePath& fp); + + std::ostream& outputStream(QtMsgType type); + + // Function called for Qt message handling + static void qtHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); + +private: + LogMessageHandler() = default; + + FilePath m_outputFilePath; + std::ofstream m_outputFile; + bool m_enableDebugLogs = true; +}; +} // namespace Mayo diff --git a/src/qtcommon/qstring_conv.cpp b/src/qtcommon/qstring_conv.cpp new file mode 100644 index 00000000..817c9e95 --- /dev/null +++ b/src/qtcommon/qstring_conv.cpp @@ -0,0 +1,41 @@ +/**************************************************************************** +** Copyright (c) 2021, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "../base/global.h" +#ifdef MAYO_OS_WINDOWS +# include +#endif + +#include "qstring_conv.h" + +namespace Mayo { + +std::string consoleToPrintable(const QString& str) +{ +#ifdef MAYO_OS_WINDOWS + const auto codepage = GetConsoleOutputCP(); + const wchar_t* source = reinterpret_cast(str.utf16()); + const int dstSize = WideCharToMultiByte(codepage, 0, source, -1, nullptr, 0, nullptr, nullptr); + std::string dst; + dst.resize(dstSize + 1); + WideCharToMultiByte(codepage, 0, source, -1, dst.data(), dstSize, nullptr, nullptr); + dst.back() = '\0'; + return dst; +#else + return str.toStdString(); // utf8 +#endif +} + +std::string consoleToPrintable(std::string_view str) +{ +#ifdef MAYO_OS_WINDOWS + return consoleToPrintable(to_QString(str)); +#else + return std::string(str); // utf8 +#endif +} + +} // namespace Mayo diff --git a/src/app/qstring_conv.h b/src/qtcommon/qstring_conv.h similarity index 96% rename from src/app/qstring_conv.h rename to src/qtcommon/qstring_conv.h index 85ce484a..c3311a7f 100644 --- a/src/app/qstring_conv.h +++ b/src/qtcommon/qstring_conv.h @@ -19,6 +19,10 @@ QString to_QString(const StringType& str) { return string_conv(str); } +// Returns 'str' converted to a "guaranteed" printable string +std::string consoleToPrintable(const QString& str); +std::string consoleToPrintable(std::string_view str/*utf8*/); + // -- // -- X -> QString // -- diff --git a/src/app/qtcore_hfuncs.h b/src/qtcommon/qtcore_hfuncs.h similarity index 100% rename from src/app/qtcore_hfuncs.h rename to src/qtcommon/qtcore_hfuncs.h diff --git a/src/app/qtcore_utils.h b/src/qtcommon/qtcore_utils.h similarity index 100% rename from src/app/qtcore_utils.h rename to src/qtcommon/qtcore_utils.h diff --git a/tests/test_app.cpp b/tests/test_app.cpp index e7c06331..a0e22498 100644 --- a/tests/test_app.cpp +++ b/tests/test_app.cpp @@ -11,15 +11,17 @@ #include "test_app.h" -#include "../src/app/filepath_conv.h" -#include "../src/app/qstring_conv.h" +#include "../src/app/app_module.h" #include "../src/app/qstring_utils.h" #include "../src/app/qtgui_utils.h" #include "../src/app/recent_files.h" #include "../src/app/theme.h" #include "../src/io_occ/io_occ.h" +#include "../src/qtcommon/filepath_conv.h" +#include "../src/qtcommon/qstring_conv.h" #include +#include #include #include #include @@ -29,6 +31,37 @@ namespace Mayo { +namespace { + +QPixmap createColorPixmap(const QColor& color, const QSize& size = QSize(64, 64)) +{ + QPixmap pix(size); + QPainter painter(&pix); + painter.fillRect(0, 0, size.width(), size.height(), color); + return pix; +} + +FilePath temporaryFilePath() +{ + QTemporaryFile file; + file.open(); + return filepathFrom(QFileInfo(file)); +} + +RecentFile createRecentFile(const QPixmap& thumbnail) +{ + QTemporaryFile file; + file.open(); + RecentFile rf; + rf.filepath = filepathFrom(QFileInfo(file)); + rf.thumbnailTimestamp = RecentFile::timestampLastModified(rf.filepath); + rf.thumbnail.imageData = QtGuiUtils::toQByteArray(thumbnail); + rf.thumbnail.imageCacheKey = thumbnail.cacheKey(); + return rf; +} + +} // namespace + void TestApp::FilePathConv_test() { const char strTestPath[] = "../as1-oc-214 - 測試文件.stp"; @@ -121,35 +154,18 @@ void TestApp::QStringUtils_text_test_data() void TestApp::RecentFiles_test() { - auto fnColorPixmap = [](const QColor& color) { - QPixmap pix(64, 64); - QPainter painter(&pix); - painter.fillRect(0, 0, 64, 64, color); - return pix; - }; - - auto fnCreateRecentFile = [](const QPixmap& thumbnail) { - QTemporaryFile file; - file.open(); - RecentFile rf; - rf.filepath = filepathFrom(QFileInfo(file)); - rf.thumbnailTimestamp = RecentFile::timestampLastModified(rf.filepath); - rf.thumbnail = thumbnail; - return rf; - }; - RecentFiles recentFiles; - recentFiles.push_back(fnCreateRecentFile(fnColorPixmap(Qt::blue))); - recentFiles.push_back(fnCreateRecentFile(fnColorPixmap(Qt::white))); - recentFiles.push_back(fnCreateRecentFile(fnColorPixmap(Qt::red))); + recentFiles.push_back(createRecentFile(createColorPixmap(Qt::blue))); + recentFiles.push_back(createRecentFile(createColorPixmap(Qt::white))); + recentFiles.push_back(createRecentFile(createColorPixmap(Qt::red))); RecentFiles recentFiles_read; { QByteArray data; QDataStream wstream(&data, QIODevice::WriteOnly); - wstream << recentFiles; + AppModule::writeRecentFiles(wstream, recentFiles); QDataStream rstream(&data, QIODevice::ReadOnly); - rstream >> recentFiles_read; + AppModule::readRecentFiles(rstream, &recentFiles_read); } QCOMPARE(recentFiles.size(), recentFiles_read.size()); @@ -159,17 +175,38 @@ void TestApp::RecentFiles_test() QCOMPARE(lhs.filepath, rhs.filepath); QVERIFY(lhs.thumbnailTimestamp != -1); QCOMPARE(lhs.thumbnailTimestamp, rhs.thumbnailTimestamp); - QCOMPARE(lhs.thumbnail.size(), rhs.thumbnail.size()); - const QImage lhsImg = lhs.thumbnail.toImage(); - const QImage rhsImg = rhs.thumbnail.toImage(); - for (int i = 0; i < lhs.thumbnail.width(); ++i) { - for (int j = 0; j < lhs.thumbnail.height(); ++j) { + QCOMPARE(lhs.thumbnail.imageData.size(), rhs.thumbnail.imageData.size()); + const QPixmap lhsPix = QtGuiUtils::toQPixmap(lhs.thumbnail.imageData); + const QPixmap rhsPix = QtGuiUtils::toQPixmap(rhs.thumbnail.imageData); + const QImage lhsImg = lhsPix.toImage(); + const QImage rhsImg = rhsPix.toImage(); + for (int i = 0; i < lhsPix.width(); ++i) { + for (int j = 0; j < lhsPix.height(); ++j) { QCOMPARE(lhsImg.pixel(i, j), rhsImg.pixel(i, j)); } } // endfor } } +void TestApp::RecentFiles_QPixmap_test() +{ + QByteArray bytes; + { + QDataStream stream(&bytes, QIODevice::WriteOnly); + stream << uint32_t(1); + stream << filepathTo(temporaryFilePath()); + stream << QPixmap();//createColorPixmap(Qt::blue); + stream << qint64(1); + } + + { + RecentFiles recentFiles; + QDataStream stream(bytes); + // Should not crash + AppModule::readRecentFiles(stream, &recentFiles); + } +} + void TestApp::StringConv_test() { const QString text = "test_éç²µ§_测试_Тест"; diff --git a/tests/test_app.h b/tests/test_app.h index f4cb5353..6ba45d09 100644 --- a/tests/test_app.h +++ b/tests/test_app.h @@ -22,6 +22,7 @@ private slots: void QStringUtils_text_test_data(); void RecentFiles_test(); + void RecentFiles_QPixmap_test(); void StringConv_test(); From c85220704c0a0b215dfca5358bca393abc858278 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 15 Mar 2024 10:25:39 +0100 Subject: [PATCH 036/101] Fix build errors for Linux/macOS --- CMakeLists.txt | 7 ++++--- src/graphics/graphics_utils.cpp | 3 ++- src/graphics/graphics_utils.h | 8 ++++++-- src/qtbackend/qt_app_translator.cpp | 2 -- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 90b48c26..0c85e417 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -471,11 +471,10 @@ add_library(MayoIO STATIC ${MayoIO_HeaderFiles} ${MayoIO_SourceFiles}) target_include_directories(MayoIO PRIVATE ${Mayo_IncludeDirectories}) target_compile_definitions(MayoIO PRIVATE ${Mayo_CompileDefinitions}) target_compile_options(MayoIO PRIVATE ${Mayo_CompileOptions}) -target_link_libraries(MayoIO PRIVATE ${MayoIO_LinkLibraries}) +target_link_libraries(MayoIO PRIVATE MayoCore ${MayoIO_LinkLibraries}) add_library(MayoIOLib INTERFACE) -target_link_libraries(MayoIOLib INTERFACE MayoIO ${MayoIO_LinkLibraries}) -target_include_directories(MayoIOLib INTERFACE ${Mayo_IncludeDirectories}) +target_link_libraries(MayoIOLib INTERFACE MayoIO MayoCoreLib ${MayoIO_LinkLibraries}) ########## # Target: MayoApp @@ -555,6 +554,8 @@ if(Mayo_BuildApp) target_compile_definitions(mayo PRIVATE ${Mayo_CompileDefinitions}) target_compile_options(mayo PRIVATE ${Mayo_CompileOptions}) target_link_libraries(mayo PRIVATE ${MayoApp_LinkLibraries}) + # Required by generated Qt UI files + target_include_directories(mayo PRIVATE ${PROJECT_SOURCE_DIR}/src/app) set_target_properties( mayo diff --git a/src/graphics/graphics_utils.cpp b/src/graphics/graphics_utils.cpp index f44e89ce..84cfea4c 100644 --- a/src/graphics/graphics_utils.cpp +++ b/src/graphics/graphics_utils.cpp @@ -10,6 +10,7 @@ #include "../base/math_utils.h" #include "../base/tkernel_utils.h" +#include #include #include #include @@ -206,7 +207,7 @@ int GraphicsUtils::AspectWindow_height(const Handle_Aspect_Window& wnd) return h; } -Handle_Aspect_DisplayConnection GraphicsUtils::AspectDisplayConnection_create() +OccHandle GraphicsUtils::AspectDisplayConnection_create() { #if (!defined(MAYO_OS_WINDOWS) && (!defined(MAYO_OS_MAC) || defined(MACOSX_USE_GLX))) return new Aspect_DisplayConnection(std::getenv("DISPLAY")); diff --git a/src/graphics/graphics_utils.h b/src/graphics/graphics_utils.h index b0913aff..256201c3 100644 --- a/src/graphics/graphics_utils.h +++ b/src/graphics/graphics_utils.h @@ -6,15 +6,19 @@ #pragma once +#include "../base/occ_handle.h" #include "graphics_object_ptr.h" #include #include -#include #include #include #include #include class Image_PixMap; +// Note: can't include Aspect_DisplayConnection.hxx as this is causing name conflicts +// with XLib in other files(with GraphicsObjectDriver::Support::None) +// This seems to happen only for OpenCascade <= v7.5 +class Aspect_DisplayConnection; namespace Mayo { @@ -51,7 +55,7 @@ struct GraphicsUtils { static int AspectWindow_width(const Handle_Aspect_Window& wnd); static int AspectWindow_height(const Handle_Aspect_Window& wnd); - static Handle_Aspect_DisplayConnection AspectDisplayConnection_create(); + static OccHandle AspectDisplayConnection_create(); static void Gfx3dClipPlane_setCappingHatch( const Handle_Graphic3d_ClipPlane& plane, Aspect_HatchStyle hatch diff --git a/src/qtbackend/qt_app_translator.cpp b/src/qtbackend/qt_app_translator.cpp index 073c160f..64a6c329 100644 --- a/src/qtbackend/qt_app_translator.cpp +++ b/src/qtbackend/qt_app_translator.cpp @@ -4,8 +4,6 @@ ** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt ****************************************************************************/ -#pragma once - #include "qt_app_translator.h" #include From d3a10fa92bb8299a0e8bbc963d85f576ba10dbdd Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 15 Mar 2024 12:16:57 +0100 Subject: [PATCH 037/101] Graphics: use OccHandle<> utility --- src/graphics/graphics_mesh_object_driver.cpp | 1 + src/graphics/graphics_scene.cpp | 8 ++- src/graphics/graphics_scene.h | 18 ++--- src/graphics/graphics_shape_object_driver.cpp | 4 ++ src/graphics/graphics_utils.cpp | 67 +++++++++++-------- src/graphics/graphics_utils.h | 62 +++++++++-------- 6 files changed, 93 insertions(+), 67 deletions(-) diff --git a/src/graphics/graphics_mesh_object_driver.cpp b/src/graphics/graphics_mesh_object_driver.cpp index 6d8b7ef3..08b0e496 100644 --- a/src/graphics/graphics_mesh_object_driver.cpp +++ b/src/graphics/graphics_mesh_object_driver.cpp @@ -16,6 +16,7 @@ #include "graphics_mesh_data_source.h" #include "graphics_utils.h" +#include #include #include #include diff --git a/src/graphics/graphics_scene.cpp b/src/graphics/graphics_scene.cpp index a589f2dd..ccede57d 100644 --- a/src/graphics/graphics_scene.cpp +++ b/src/graphics/graphics_scene.cpp @@ -14,6 +14,8 @@ #include #include +#include + namespace Mayo { // Defined in graphics_create_driver.cpp @@ -217,12 +219,12 @@ void GraphicsScene::deactivateObjectSelection(const GraphicsObjectPtr &object) d->m_aisContext->Deactivate(object); } -void GraphicsScene::addSelectionFilter(const Handle_SelectMgr_Filter& filter) +void GraphicsScene::addSelectionFilter(const OccHandle& filter) { d->m_aisContext->AddFilter(filter); } -void GraphicsScene::removeSelectionFilter(const Handle_SelectMgr_Filter& filter) +void GraphicsScene::removeSelectionFilter(const OccHandle& filter) { d->m_aisContext->RemoveFilter(filter); } @@ -308,7 +310,7 @@ void GraphicsScene::toggleOwnerSelection(const GraphicsOwnerPtr& gfxOwner) d->m_aisContext->AddOrRemoveSelected(gfxOwner, false); } -void GraphicsScene::highlightAt(int xPos, int yPos, const Handle_V3d_View& view) +void GraphicsScene::highlightAt(int xPos, int yPos, const OccHandle& view) { d->m_aisContext->MoveTo(xPos, yPos, view, false); } diff --git a/src/graphics/graphics_scene.h b/src/graphics/graphics_scene.h index 68ccc554..b15a812f 100644 --- a/src/graphics/graphics_scene.h +++ b/src/graphics/graphics_scene.h @@ -6,6 +6,7 @@ #pragma once +#include "../base/occ_handle.h" #include "../base/signal.h" #include "graphics_object_ptr.h" #include "graphics_owner_ptr.h" @@ -13,7 +14,6 @@ #include #include #include -#include namespace Mayo { @@ -28,14 +28,14 @@ class GraphicsScene { GraphicsScene(const GraphicsScene&) = delete; GraphicsScene& operator=(const GraphicsScene&) = delete; - opencascade::handle createV3dView(); + OccHandle createV3dView(); - const opencascade::handle& v3dViewer() const; - const opencascade::handle& mainSelector() const; + const OccHandle& v3dViewer() const; + const OccHandle& mainSelector() const; bool hiddenLineDrawingOn() const; - const opencascade::handle& drawerDefault() const; - const opencascade::handle& drawerHighlight(Prs3d_TypeOfHighlight style) const; + const OccHandle& drawerDefault() const; + const OccHandle& drawerHighlight(Prs3d_TypeOfHighlight style) const; enum AddObjectFlag { AddObjectDefault = 0, @@ -57,8 +57,8 @@ class GraphicsScene { void deactivateObjectSelection(const GraphicsObjectPtr& object, int mode); void deactivateObjectSelection(const GraphicsObjectPtr& object); - void addSelectionFilter(const Handle_SelectMgr_Filter& filter); - void removeSelectionFilter(const Handle_SelectMgr_Filter& filter); + void addSelectionFilter(const OccHandle& filter); + void removeSelectionFilter(const OccHandle& filter); void clearSelectionFilters(); void setObjectDisplayMode(const GraphicsObjectPtr& object, int displayMode); @@ -79,7 +79,7 @@ class GraphicsScene { void setSelectionMode(SelectionMode mode); const GraphicsOwnerPtr& currentHighlightedOwner() const; - void highlightAt(int xPos, int yPos, const Handle_V3d_View& view); + void highlightAt(int xPos, int yPos, const OccHandle& view); void select(); int selectedCount() const; diff --git a/src/graphics/graphics_shape_object_driver.cpp b/src/graphics/graphics_shape_object_driver.cpp index 484e7bbd..e63267a9 100644 --- a/src/graphics/graphics_shape_object_driver.cpp +++ b/src/graphics/graphics_shape_object_driver.cpp @@ -14,7 +14,11 @@ #include "graphics_utils.h" #include +#include #include +#include +#include +#include #include namespace Mayo { diff --git a/src/graphics/graphics_utils.cpp b/src/graphics/graphics_utils.cpp index 84cfea4c..4064b0f7 100644 --- a/src/graphics/graphics_utils.cpp +++ b/src/graphics/graphics_utils.cpp @@ -10,13 +10,20 @@ #include "../base/math_utils.h" #include "../base/tkernel_utils.h" +#include +#include #include +#include +#include #include #include +#include #include #include #include #include +#include +#include #include #include @@ -27,7 +34,8 @@ namespace Mayo { namespace Internal { static void AisContext_setObjectVisible( - AIS_InteractiveContext* ptrContext, const Handle_AIS_InteractiveObject& object, bool on) + AIS_InteractiveContext* ptrContext, const OccHandle& object, bool on + ) { if (ptrContext && object) { if (on) @@ -39,21 +47,22 @@ static void AisContext_setObjectVisible( } // namespace Internal -void GraphicsUtils::V3dView_fitAll(const Handle_V3d_View& view) +void GraphicsUtils::V3dView_fitAll(const OccHandle& view) { view->ZFitAll(); view->FitAll(0.01, false/*dontUpdateView*/); } bool GraphicsUtils::V3dView_hasClipPlane( - const Handle_V3d_View& view, const Handle_Graphic3d_ClipPlane& plane) + const OccHandle& view, const OccHandle& plane + ) { - const Handle_Graphic3d_SequenceOfHClipPlane& seqClipPlane = view->ClipPlanes(); + const OccHandle& seqClipPlane = view->ClipPlanes(); if (seqClipPlane.IsNull() || seqClipPlane->Size() == 0) return false; for (Graphic3d_SequenceOfHClipPlane::Iterator it(*seqClipPlane); it.More(); it.Next()) { - const Handle_Graphic3d_ClipPlane& candidate = it.Value(); + const OccHandle& candidate = it.Value(); if (candidate.get() == plane.get()) return true; } @@ -61,7 +70,7 @@ bool GraphicsUtils::V3dView_hasClipPlane( return false; } -gp_Pnt GraphicsUtils::V3dView_to3dPosition(const Handle_V3d_View& view, double x, double y) +gp_Pnt GraphicsUtils::V3dView_to3dPosition(const OccHandle& view, double x, double y) { double xEye, yEye, zEye, xAt, yAt, zAt; view->Eye(xEye, yEye, zEye); @@ -83,7 +92,7 @@ gp_Pnt GraphicsUtils::V3dView_to3dPosition(const Handle_V3d_View& view, double x return ElSLib::Value(pntConvertedOnPlane.X(), pntConvertedOnPlane.Y(), planeView); } -bool GraphicsUtils::V3dViewer_isGridActive(const Handle_V3d_Viewer& viewer) +bool GraphicsUtils::V3dViewer_isGridActive(const OccHandle& viewer) { #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) return viewer->IsGridActive(); @@ -92,7 +101,7 @@ bool GraphicsUtils::V3dViewer_isGridActive(const Handle_V3d_Viewer& viewer) #endif } -Handle_Aspect_Grid GraphicsUtils::V3dViewer_grid(const Handle_V3d_Viewer& viewer) +OccHandle GraphicsUtils::V3dViewer_grid(const OccHandle& viewer) { #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) return viewer->Grid(false/*dontCreate*/); @@ -101,26 +110,29 @@ Handle_Aspect_Grid GraphicsUtils::V3dViewer_grid(const Handle_V3d_Viewer& viewer #endif } -GraphicsUtils::AspectGridColors GraphicsUtils::V3dViewer_gridColors(const Handle_V3d_Viewer& viewer) +GraphicsUtils::AspectGridColors GraphicsUtils::V3dViewer_gridColors(const OccHandle& viewer) { AspectGridColors colors; - Handle_Aspect_Grid gridAspect = V3dViewer_grid(viewer); + OccHandle gridAspect = V3dViewer_grid(viewer); if (gridAspect) gridAspect->Colors(colors.base, colors.tenth); return colors; } -void GraphicsUtils::V3dViewer_setGridColors(const Handle_V3d_Viewer &viewer, const AspectGridColors &colors) +void GraphicsUtils::V3dViewer_setGridColors( + const OccHandle& viewer, const AspectGridColors& colors + ) { - Handle_Aspect_Grid gridAspect = V3dViewer_grid(viewer); + OccHandle gridAspect = V3dViewer_grid(viewer); if (gridAspect) gridAspect->SetColors(colors.base, colors.tenth); } void GraphicsUtils::AisContext_eraseObject( - const Handle_AIS_InteractiveContext& context, - const Handle_AIS_InteractiveObject& object) + const OccHandle& context, + const OccHandle& object + ) { if (!object.IsNull() && !context.IsNull()) { context->Erase(object, false/*dontUpdateViewer*/); @@ -131,14 +143,15 @@ void GraphicsUtils::AisContext_eraseObject( } void GraphicsUtils::AisContext_setObjectVisible( - const Handle_AIS_InteractiveContext& context, - const Handle_AIS_InteractiveObject& object, - bool on) + const OccHandle& context, + const OccHandle& object, + bool on + ) { Internal::AisContext_setObjectVisible(context.get(), object, on); } -AIS_InteractiveContext* GraphicsUtils::AisObject_contextPtr(const GraphicsObjectPtr& object) +AIS_InteractiveContext* GraphicsUtils::AisObject_contextPtr(const OccHandle& object) { if (!object) return nullptr; @@ -150,18 +163,18 @@ AIS_InteractiveContext* GraphicsUtils::AisObject_contextPtr(const GraphicsObject #endif } -bool GraphicsUtils::AisObject_isVisible(const GraphicsObjectPtr& object) +bool GraphicsUtils::AisObject_isVisible(const OccHandle& object) { const AIS_InteractiveContext* ptrContext = AisObject_contextPtr(object); return ptrContext ? ptrContext->IsDisplayed(object) : false; } -void GraphicsUtils::AisObject_setVisible(const GraphicsObjectPtr& object, bool on) +void GraphicsUtils::AisObject_setVisible(const OccHandle& object, bool on) { Internal::AisContext_setObjectVisible(AisObject_contextPtr(object), object, on); } -Bnd_Box GraphicsUtils::AisObject_boundingBox(const GraphicsObjectPtr& object) +Bnd_Box GraphicsUtils::AisObject_boundingBox(const OccHandle& object) { Bnd_Box box; if (object.IsNull()) @@ -169,7 +182,7 @@ Bnd_Box GraphicsUtils::AisObject_boundingBox(const GraphicsObjectPtr& object) // Ensure bounding box is calculated #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) - for (Handle_PrsMgr_Presentation prs : object->Presentations()) { + for (OccHandle prs : object->Presentations()) { if (prs->Mode() == object->DisplayMode() && !prs->CStructure()->BoundingBox().IsValid()) prs->CalculateBoundBox(); } @@ -187,7 +200,7 @@ Bnd_Box GraphicsUtils::AisObject_boundingBox(const GraphicsObjectPtr& object) return box; } -int GraphicsUtils::AspectWindow_width(const Handle_Aspect_Window& wnd) +int GraphicsUtils::AspectWindow_width(const OccHandle& wnd) { if (wnd.IsNull()) return 0; @@ -197,7 +210,7 @@ int GraphicsUtils::AspectWindow_width(const Handle_Aspect_Window& wnd) return w; } -int GraphicsUtils::AspectWindow_height(const Handle_Aspect_Window& wnd) +int GraphicsUtils::AspectWindow_height(const OccHandle& wnd) { if (wnd.IsNull()) return 0; @@ -217,7 +230,7 @@ OccHandle GraphicsUtils::AspectDisplayConnection_creat } void GraphicsUtils::Gfx3dClipPlane_setCappingHatch( - const Handle_Graphic3d_ClipPlane& plane, Aspect_HatchStyle hatch) + const OccHandle& plane, Aspect_HatchStyle hatch) { if (hatch == Aspect_HS_SOLID) plane->SetCappingHatchOff(); @@ -227,14 +240,14 @@ void GraphicsUtils::Gfx3dClipPlane_setCappingHatch( plane->SetCappingHatch(hatch); } -void GraphicsUtils::Gfx3dClipPlane_setNormal(const Handle_Graphic3d_ClipPlane& plane, const gp_Dir& n) +void GraphicsUtils::Gfx3dClipPlane_setNormal(const OccHandle& plane, const gp_Dir& n) { const double planePos = MathUtils::planePosition(plane->ToPlane()); const gp_Vec placement(planePos * gp_Vec(n)); plane->SetEquation(gp_Pln(placement.XYZ(), n)); } -void GraphicsUtils::Gfx3dClipPlane_setPosition(const Handle_Graphic3d_ClipPlane& plane, double pos) +void GraphicsUtils::Gfx3dClipPlane_setPosition(const OccHandle& plane, double pos) { const gp_Dir& n = plane->ToPlane().Axis().Direction(); if (MathUtils::isReversedStandardDir(n)) diff --git a/src/graphics/graphics_utils.h b/src/graphics/graphics_utils.h index 256201c3..69884bcb 100644 --- a/src/graphics/graphics_utils.h +++ b/src/graphics/graphics_utils.h @@ -7,18 +7,24 @@ #pragma once #include "../base/occ_handle.h" -#include "graphics_object_ptr.h" -#include -#include -#include -#include -#include -#include -class Image_PixMap; + +#include +#include +#include + // Note: can't include Aspect_DisplayConnection.hxx as this is causing name conflicts // with XLib in other files(with GraphicsObjectDriver::Support::None) // This seems to happen only for OpenCascade <= v7.5 class Aspect_DisplayConnection; +class Aspect_Grid; +class Aspect_Window; +class AIS_InteractiveContext; +class AIS_InteractiveObject; +class Graphic3d_ClipPlane; +class Image_PixMap; +class V3d_View; +class V3d_Viewer; +class gp_Dir; namespace Mayo { @@ -29,42 +35,42 @@ struct GraphicsUtils { Quantity_Color tenth; }; - static void V3dView_fitAll(const Handle_V3d_View& view); - static bool V3dView_hasClipPlane(const Handle_V3d_View& view, const Handle_Graphic3d_ClipPlane& plane); - static gp_Pnt V3dView_to3dPosition(const Handle_V3d_View& view, double x, double y); + static void V3dView_fitAll(const OccHandle& view); + static bool V3dView_hasClipPlane(const OccHandle& view, const OccHandle& plane); + static gp_Pnt V3dView_to3dPosition(const OccHandle& view, double x, double y); - static bool V3dViewer_isGridActive(const Handle_V3d_Viewer& viewer); - static Handle_Aspect_Grid V3dViewer_grid(const Handle_V3d_Viewer& viewer); - static AspectGridColors V3dViewer_gridColors(const Handle_V3d_Viewer& viewer); - static void V3dViewer_setGridColors(const Handle_V3d_Viewer& viewer, const AspectGridColors& colors); + static bool V3dViewer_isGridActive(const OccHandle& viewer); + static OccHandle V3dViewer_grid(const OccHandle& viewer); + static AspectGridColors V3dViewer_gridColors(const OccHandle& viewer); + static void V3dViewer_setGridColors(const OccHandle& viewer, const AspectGridColors& colors); static void AisContext_eraseObject( - const Handle_AIS_InteractiveContext& context, - const Handle_AIS_InteractiveObject& object + const OccHandle& context, + const OccHandle& object ); static void AisContext_setObjectVisible( - const Handle_AIS_InteractiveContext& context, - const Handle_AIS_InteractiveObject& object, + const OccHandle& context, + const OccHandle& object, bool on ); - static AIS_InteractiveContext* AisObject_contextPtr(const GraphicsObjectPtr& object); - static bool AisObject_isVisible(const GraphicsObjectPtr& object); - static void AisObject_setVisible(const GraphicsObjectPtr& object, bool on); - static Bnd_Box AisObject_boundingBox(const GraphicsObjectPtr& object); + static AIS_InteractiveContext* AisObject_contextPtr(const OccHandle& object); + static bool AisObject_isVisible(const OccHandle& object); + static void AisObject_setVisible(const OccHandle& object, bool on); + static Bnd_Box AisObject_boundingBox(const OccHandle& object); - static int AspectWindow_width(const Handle_Aspect_Window& wnd); - static int AspectWindow_height(const Handle_Aspect_Window& wnd); + static int AspectWindow_width(const OccHandle& wnd); + static int AspectWindow_height(const OccHandle& wnd); static OccHandle AspectDisplayConnection_create(); static void Gfx3dClipPlane_setCappingHatch( - const Handle_Graphic3d_ClipPlane& plane, Aspect_HatchStyle hatch + const OccHandle& plane, Aspect_HatchStyle hatch ); static void Gfx3dClipPlane_setNormal( - const Handle_Graphic3d_ClipPlane& plane, const gp_Dir& n + const OccHandle& plane, const gp_Dir& n ); static void Gfx3dClipPlane_setPosition( - const Handle_Graphic3d_ClipPlane& plane, double pos + const OccHandle& plane, double pos ); static bool ImagePixmap_flipY(Image_PixMap& pixmap); From 2f3872c3ee88d2f6f50b1bb8c3635671ff7c1051 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 21 Mar 2024 18:51:21 +0100 Subject: [PATCH 038/101] I18N: update translations --- i18n/i18n.pro | 30 +- i18n/mayo.qrc | 6 + i18n/mayo_en.qm | Bin 52492 -> 56609 bytes i18n/mayo_en.ts | 790 +++++++++++++++++++++++++++++++++--------------- i18n/mayo_fr.qm | Bin 60740 -> 69040 bytes i18n/mayo_fr.ts | 786 ++++++++++++++++++++++++++++++----------------- 6 files changed, 1080 insertions(+), 532 deletions(-) create mode 100644 i18n/mayo.qrc diff --git a/i18n/i18n.pro b/i18n/i18n.pro index 7adaae4b..6657fae9 100644 --- a/i18n/i18n.pro +++ b/i18n/i18n.pro @@ -1,26 +1,34 @@ HEADERS += \ + $$files(../src/app/*.h) \ + $$files(../src/app/windows/*.h) \ $$files(../src/base/*.h) \ - $$files(../src/io_occ/*.h) \ + $$files(../src/cli/*.h) \ + $$files(../src/graphics/*.h) \ + $$files(../src/gui/*.h) \ + $$files(../src/io_assimp/*.h) \ $$files(../src/io_dxf/*.h) \ $$files(../src/io_gmio/*.h) \ $$files(../src/io_image/*.h) \ + $$files(../src/io_occ/*.h) \ + $$files(../src/io_off/*.h) \ $$files(../src/io_ply/*.h) \ - $$files(../src/graphics/*.h) \ - $$files(../src/gui/*.h) \ - $$files(../src/app/*.h) \ - $$files(../src/app/windows/*.h) \ + $$files(../src/measure/*.h) \ + SOURCES += \ + $$files(../src/app/*.cpp) \ + $$files(../src/app/windows/*.cpp) \ $$files(../src/base/*.cpp) \ - $$files(../src/io_occ/*.cpp) \ + $$files(../src/cli/*.cpp) \ + $$files(../src/graphics/*.cpp) \ + $$files(../src/gui/*.cpp) \ + $$files(../src/io_assimp/*.cpp) \ $$files(../src/io_dxf/*.cpp) \ $$files(../src/io_gmio/*.cpp) \ $$files(../src/io_image/*.cpp) \ - $$files(../src/io_ply/*.cpp) \ - $$files(../src/graphics/*.cpp) \ - $$files(../src/gui/*.cpp) \ - $$files(../src/app/*.cpp) \ - $$files(../src/app/windows/*.cpp) \ + $$files(../src/io_occ/*.cpp) \ + $$files(../src/io_off/*.cpp) \ + $$files(../src/measure/*.cpp) \ \ messages.cpp \ diff --git a/i18n/mayo.qrc b/i18n/mayo.qrc new file mode 100644 index 00000000..e7c02270 --- /dev/null +++ b/i18n/mayo.qrc @@ -0,0 +1,6 @@ + + +mayo_en.qm +mayo_fr.qm + + diff --git a/i18n/mayo_en.qm b/i18n/mayo_en.qm index d83aed1248b5d3a2e2c95fd3d0b424cc34e5e44c..20c191e56c7ca4eb49ebd2adc49a1d63c454cc08 100644 GIT binary patch delta 7972 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 delta 4822 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 Mayo::AppModule - + en English - + fr French @@ -492,30 +492,98 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho + + Mayo::BRepMeasureError + + + Entity must be a vertex + + + + + Entity must be a circular edge + + + + + Entity must be a shape(BREP) + + + + + Entity must be a geometric or polygon edge + + + + + Entity must be a geometric or triangulation face + + + + + Computation of minimum distance failed + + + + + Unable to find center of the shape + + + + + All entities must be edges + + + + + Entity must be a linear edge + + + + + All entities must be faces + + + + + Entities must not be parallel + + + + + Bounding box computed is void + + + + + Unknown error + + + Mayo::CliExport - + Mesh BRep shapes - + Imported - + Exported {} - + Importing... - + Exporting {}... @@ -805,12 +873,12 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho - + System Information... - + Copy to Clipboard @@ -823,38 +891,38 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho - + Mayo By Fougue Ltd. - + Version %1 (%2bit) - + Built on %1 at %2 - + Qt %1 - + OpenCascade %1 - - gmio %1 + + %1 By %2 - - %1 By %2 + + %1 %2 @@ -866,77 +934,87 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho - - ShapeType=%1, Evolution=%2 - - - - + Yes - + No - + File Size: %1<br>Dimensions: %2x%3 Depth: %4 - + Error when loading texture file(invalid path?) - + + %1,offset:%2 + + + + + <data> + + + + Shape Shape - + Color Color - + Material - + VisMaterial - + Dimension - + Datum - + GeomTolerance - + Error - + + ShapeType=%1, ShapeLocation=%2, Evolution=%3 + + + + This document is not suitable for XDE - + Attributes @@ -988,7 +1066,7 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho - + Error @@ -1003,17 +1081,17 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho - - Error when writing to'%1' + + Error when writing to '%1' - + Restore values for default section only - + Restore values for the whole group @@ -1147,37 +1225,37 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Mayo::GraphicsMeshObjectDriver - + Mesh_Wireframe [Mesh] Wireframe - + Mesh_Shaded [Mesh] Shaded - + Mesh_Shrink [Mesh] Shrink - + color Color - + edgeColor Edge Color - + showEdges Show Edges - + showNodes Show Nodes @@ -1235,65 +1313,84 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Mayo::GraphicsShapeObjectDriver - + Shape_Wireframe [Shape] Wireframe - + Shape_HiddenLineRemoval [Shape] Hidden Line Removal - + Shape_Shaded [Shape] Shaded - + Shape_ShadedWithFaceBoundary [Shape] Shaded With Edges + + Mayo::IO::AssimpReaderI18N + + + LINE primitives not supported yet + + + + + Some primitive not supported + + + + + Texture not found: {} +Tried: + + + Mayo::IO::DxfReader::Properties - + Scale entities according some factor - + Import text/dimension objects - + Group all objects within a layer into a single compound shape - + Name of the font to be used when creating shape for text objects - + scaling Scaling - + importAnnotations Import annotations - + groupLayers Group objects by layer - + fontNameForTextObjects Font for TEXT objects @@ -1343,27 +1440,27 @@ Only applicable if option `{}` is on - + float64Format 64bit Float Format - + float64Precision 64bit Float Precision - + createZipArchive Create ZIP Archive - + zipEntryFilename ZIP Entry Filename - + useZip64 Use ZIP64 extensions @@ -1586,67 +1683,67 @@ Only applicable if option `{}` is on Coordinates Converter - + transformationFormat Transformation Format - + format Target Format - + forceExportUV Force UV Export - + Source coordinate system transformation - + Target coordinate system transformation - + Preferred transformation format for writing into glTF file - + Export UV coordinates even if there is no mapped texture - + Automatically choose most compact representation between Mat4 and TRS - + 4x4 transformation matrix - + Transformation decomposed into Translation vector, Rotation quaternion and Scale factor(T * R * S) - + Name format for exporting nodes - + Name format for exporting meshes - + Write image textures into target file. If set to `false` then texture images will be written as separate files. @@ -1655,14 +1752,14 @@ Applicable only if option `{0}` is set to `{1}` - + Merge faces within a single part. May reduce JSON size thanks to smaller number of primitive arrays - + Prefer keeping 16-bit indexes while merging face. May reduce binary data size thanks to smaller triangle indexes. @@ -1671,42 +1768,42 @@ Applicable only if option `{}` is on - + inputCoordinateSystem Input Coordinate System - + outputCoordinateSystem Output Coordinate System - + nodeNameFormat Node Name Format - + meshNameFormat Mesh Name Format - + embedTextures Embed Textures - + mergeFaces Merge Faces - + keepIndices16b Keep 16bit Indices - + Option supported from OpenCascade ≥ v7.6 [option={}, actual version={}] @@ -1724,22 +1821,22 @@ Applicable only if option `{}` is on Mayo::IO::OccIgesReader::Properties - + bsplineContinuity BSpline Continuity - + surfaceCurveMode Surface Curve Mode - + readFaultyEntities Read Faulty Entities - + readOnlyVisibleEntities Read Only Visible Entities @@ -1749,7 +1846,7 @@ Applicable only if option `{}` is on - + Preference for the computation of curves in case of 2D/3D inconsistency in an entity which has both 2D and 3D representations. Concerned entity types are 141 (Boundary), 142 (CurveOnSurface) and 508 (Loop). These are entities representing a contour lying on a surface, which is translated to a TopoDS_Wire, formed by TopoDS_Edges. Each TopoDS_Edge must have a 3D curve and a 2D curve that reference the surface. @@ -1760,47 +1857,47 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b - + Read failed entities - + Curves are taken as they are in the IGES file. C0 entities of Open CASCADE may be produced - + If an IGES BSpline, Spline or CopiousData curve is C0 continuous, it is broken down into pieces of C1 continuous Geom_BSplineCurve - + IGES Spline curves are broken down into pieces of C2 continuity. If C2 cannot be ensured, the Spline curves will be broken down into pieces of C1 continuity - + Use the preference flag value in the entity's `Parameter Data` section - + The 2D is used to rebuild the 3D in case of their inconsistency - + The 2D is always used to rebuild the 3D (even if 3D is present in the file) - + The 3D is used to rebuild the 2D in case of their inconsistency - + The 3D is always used to rebuild the 2D (even if 2D is present in the file) @@ -1808,32 +1905,32 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Mayo::IO::OccIgesWriter::Properties - + brepMode BRep Mode - + planeMode Plane Mode - + lengthUnit Length Unit - + Indicates if planes should be saved as Bsplines or Planes (type 108). Writing p-curves on planes is disabled - + OpenCascade TopoDS_Faces will be translated into IGES 144 (Trimmed Surface) entities, no BRep entities will be written to the IGES file - + OpenCascade TopoDS_Faces will be translated into IGES 510 (Face) entities, the IGES file will contain BRep entities @@ -1858,22 +1955,22 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Coordinates Converter - + Source coordinate system transformation - + Target coordinate system transformation - + inputCoordinateSystem Input Coordinate System - + outputCoordinateSystem Output Coordinate System @@ -1881,114 +1978,114 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Mayo::IO::OccStepReader::Properties - + productContext Product Context - + assemblyLevel Assembly Level - + preferredShapeRepresentation Preferred Shape Representation - + readShapeAspect Read Shape Aspect - + readSubShapesNames Read Names of sub Shapes - + encoding Encoding - + When reading AP 209 STEP files, allows selecting either only `design` or `analysis`, or both types of products for translation Note that in AP 203 and AP214 files all products should be marked as `design`, so if this mode is set to `analysis`, nothing will be read - + Specifies which data should be read for the products found in the STEP file - + Specifies preferred type of representation of the shape of the product, in case if a STEP file contains more than one representation (i.e. multiple `PRODUCT_DEFINITION_SHAPE` entities) for a single product - + Defines whether shapes associated with the `PRODUCT_DEFINITION_SHAPE` entity of the product via `SHAPE_ASPECT` should be translated. This kind of association was used for the representation of hybrid models (i.e. models whose shape is composed of different types of representations) in AP 203 files before 1998, but it is also used to associate auxiliary information with the sub-shapes of the part. Though STEP translator tries to recognize such cases correctly, this parameter may be useful to avoid unconditionally translation of shapes associated via `SHAPE_ASPECT` entities. - + Indicates whether to read sub-shape names from 'Name' attributes of STEP Representation Items - + Translate only products that have `PRODUCT_DEFINITION_CONTEXT` with field `life_cycle_stage` set to `design` - + Translate only products that have `PRODUCT_DEFINITION_CONTEXT` with field `life_cycle_stage` set to `analysis` - + Translates all products - + Translate the assembly structure and shapes associated with parts only(not with sub-assemblies) - + Translate only the assembly structure without shapes(a structure of empty compounds). This mode can be useful as an intermediate step in applications requiring specialized processing of assembly parts - + Translate only shapes associated with the product, ignoring the assembly structure (if any). This can be useful to translate only a shape associated with specific product, as a complement to assembly mode - + Translate both the assembly structure and all associated shapes. If both shape and sub-assemblies are associated with the same product, all of them are read and put in a single compound - + Translate all representations(if more than one, put in compound) - + Shift Japanese Industrial Standards - + EUC(Extended Unix Code), multi-byte encoding primarily for Japanese, Korean, and simplified Chinese - + GB(Guobiao) encoding for Simplified Chinese @@ -1996,108 +2093,108 @@ This kind of association was used for the representation of hybrid models (i.e. Mayo::IO::OccStepWriter::Properties - + schema Schema - + lengthUnit Length Unit - + assemblyMode Assembly Mode - + freeVertexMode Mode for Free Vertices - + writeParametericCurves Write Parametric Curves - + writeSubShapesNames Write Names of sub Shapes - + headerAuthor Author(header) - + headerOrganization Organization(header) - + headerOriginatingSystem Originating system(header) - + headerDescription Description(header) - + Version of schema used for the output STEP file - + Defines a unit in which the STEP file should be written. If set to unit other than millimeter, the model is converted to these units during the translation - + Parameter to write all free vertices in one SDR (name and style of vertex are lost) or each vertex in its own SDR (name and style of vertex are exported) - + All free vertices are united into one compound and exported in one shape definition representation (vertex name and style are lost) - + Each vertex is exported in its own `SHAPE DEFINITION REPRESENTATION`(vertex name and style are not lost, but the STEP file size increases) - + Indicates whether parametric curves (curves in parametric space of surface) should be written into the STEP file. It can be disabled in order to minimize the size of the resulting file. - + Indicates whether to write sub-shape names to 'Name' attributes of STEP Representation Items - + Author attribute in STEP header - + Organization(of author) attribute in STEP header - + Originating system attribute in STEP header - + Description attribute in STEP header @@ -2120,13 +2217,13 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::IO::OccStlWriterI18N - + targetFormat Target Format - - + + Not all BRep faces are meshed @@ -2144,11 +2241,53 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::IO::OccVrmlWriter::Properties - + shapeRepresentation Shape Representation + + Mayo::IO::OffReaderI18N + + + Can't open input file + + + + + + Unexpected end of file + + + + + Wrong header keyword(should be [C][N][4]OFF + + + + + No vertex or face count + + + + + No vertex coordinates at current line + + + + + Inconsistent vertex count of face + + + + + Mayo::IO::OffWriterI18N + + + Failed to open file + + + Mayo::IO::PlyWriterI18N @@ -2200,23 +2339,23 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::IO::System - + Reading file - + Unknown format - + Error during import of '{}' {} - + No supporting reader @@ -2232,33 +2371,33 @@ It can be disabled in order to minimize the size of the resulting file. - + File transfer problem - + Error during export to '{}' {} - + No supporting writer - + Transfer - + Write - + File write problem @@ -2266,104 +2405,156 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::Main - + Theme for the UI(classic|dark) - + name - + + Writes log messages into output file - + + Don't filter out debug log messages in release build - - Disable progress reporting in console output(CLI-mode only) - - - - + + Show detailed system information and quit - + + files - + Files to open at startup, optionally - + + [files...] - + Execute unit tests and exit application - + + OpenCascade settings file doesn't exist or is not readable [path=%1] - + + OpenCascade settings file could not be loaded with QSettings [path=%1] - + + Failed to load translation file [path=%1] - + Settings file(INI format) to load at startup - + Mayo the opensource 3D CAD viewer and converter - - - + + + + + + filepath File Path - + + mayo-conv the opensource CAD converter + + + + + Display help on commandline options + + + + + Display version information + + + + + Use settings file(INI format) for the conversion. When this option isn't specified then cached settings are used + + + + + Cache settings file provided with --use-settings for further use + + + + + Write settings cache to an output file(INI format) + + + + Export opened files into an output file, can be repeated for different formats(eg. -e file.stp -e file.igs...) - + + Disable progress reporting in console output + + + + + Files to open(import) + + + + + Error when writing to '%1' + + + + + Failed to load application settings file [path=%1] - + No input files -> nothing to export - + Failed to load theme '%1' @@ -2405,13 +2596,13 @@ It can be disabled in order to minimize the size of the resulting file. Import - + Warning - - + + Error @@ -2419,8 +2610,55 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::MeasureDisplayI18N + + Sum + + + + + (<font color="#FF5500">X</font>{0} <font color="#55FF00">Y</font>{1} <font color="#0077FF">Z</font>{2}){3} + + + + + X{0} Y{1} Z{2} + + + + + Diameter: {0}{1} + + + + + Ø{0} + + + + + Angle: {0}{1} + + + + + + {0}: {1}{2} + + + + + Length + + + + Area - Area + Area + + + + Min point: {0}<br>Max point: {1}<br>Size: {2} x {3} x {4}{5}<br>Volume: {6}{7} + @@ -2807,67 +3045,67 @@ Last modified: %3 Mayo::WidgetGuiDocument - + Fit All - + Edit Grid - + Edit clip planes - + Explode assemblies - + Measure shapes - + Isometric - + Back - + Front - + Left - + Right - + Top - + Bottom - + <b>Left-click</b>: popup menu of pre-defined views <b>CTRL+Left-click</b>: apply '%1' view @@ -2876,52 +3114,52 @@ Last modified: %3 Mayo::WidgetHomeFiles - + New Document - + Create and add an empty document where you can import files - + Open Document(s) - + Select files to load and open as distinct documents - + today %1 - + yersterday %1 - - + + %1 %2 - + %1 days ago %2 - + %1 Size: %2 @@ -3020,132 +3258,182 @@ Read: %5 - + Measure - + Millimeter(mm) - + Centimeter(cm) - + Meter(m) - + Inch(in) - + Foot(ft) - + Yard(yd) - + Degree(°) - + Radian(rad) - + + Volume Unit + + + + Vertex Position - + Circle Center - + Circle Diameter - + Min Distance - + Center-to-center Distance - + Length - + + Bounding Box + + + + + Cubic Millimeter(mm³) + + + + + Cubic Centimeter(cm³) + + + + + Cubic Meter(m³) + + + + + Cubic Inch(in³) + + + + + Cubic Foot(ft³) + + + + + Liter(L) + + + + + Imperial Gallon(GBgal) + + + + + US Gallon(USgal) + + + + Square Millimeter(mm²) - + Square Centimeter(cm²) - + Square Meter(m²) - + Square Inch(in²) - + Square Foot(ft²) - + Square Yard(yd²) - + Angle - + Surface Area - + Length Unit - + Angle Unit - + Select entities to measure @@ -3163,7 +3451,7 @@ Read: %5 - + <unnamed> @@ -3176,7 +3464,7 @@ Read: %5 Name Format Of Assembly Instances - + Show {} diff --git a/i18n/mayo_fr.qm b/i18n/mayo_fr.qm index dbaa78ea56aee779be1938e21505640e447a1640..f1c86d5a1cc3099993203fb7befb8ff556631a1f 100644 GIT binary patch delta 10931 zcmbVR34BcF*FLk{S(6N6i7g#si7kytENR3N2_XrRSc96(BEw`RW{HHx*wtEkQCscQ zR!c)arKMGh*1l`iQd<lH%+yway@++e24IsaU9vkE#TxUT3OftGYZRX<2-kw=Ren*LZ#MU9@h{7mge^3_X;t_G6)w23IcFXS{B=qJjP&qaB4gD9`>66LJ}qI~EP<>T$d z$>(~p!F1xP4ImnABd*a&c%&t+J^ciFQsT zJ?9UowNxu%4blAvR3{4yv-eSG1fYv6mY2s;!%t2T_1Z)YkJiOTzfi;5;V`rYHMZyB z{v%QDo=PqK5waBxC}D3RQFsJ(oU@0hLkx9F!E@IiQKxx-5-rZ;Va`E11b zICVCHP6;bTc`{j)PlBoQ6vRB?A|=hk?;YP2Wpb`4Q>Tk^&t6J;{xQ+^KPY)lI?>N# zDNPPeoXVlJ?{@=9C8aB{&-(GyJ99Kqyp_DYpFINPA5fn{#BkqO>ho1R(aI?5Qv}F5 zYeo6jVd^_MgsA5+>Nj>P(eLf4|5I?HXMGwlV2b=I!m!C9JfnYMtc_1TNc#D>|hhd&4 zv_A11(Vb}8btV=ZX++-~3?teyh`t?)b<@2&=;*@Vu+cF(HhetM`(x-dj~MqmLPeXd zf^=?C{-UAVn|Y#-X3(EQ5c3fQ68amY%Xld9UxNGmB1zyK#QN*6BsK2B(4&V$d48az z=GM9(+cHUTd8}Wkk+c|$^{%%hEqF7L_mf59m({Z*85V4C(;+dxi`cc=Eipe@1oCx~ z6g&Yp8r6|_jgdqxc*&TW2Z$t}N~UFbh#IRUAAHk*$hK4R-wD}_kY55%komS#Ey{MlWOOEhWuZnla@<>=ie-n29tv51Cz8CLP*`V zNC&h+Xg=RA9oh~^+^8kZ(_JH4v_(4Hc%5j~HEF?2)XlWNr4vS3Q8(vF-*1cO4sp^c z(!+@TKhl|J9ujT7C7sn`1yQ6_y8NICif2fdKP)85sx94NX@>mY(@na&b|0d9VbVQe ztwD;8(tQdT6f2V+d|p7bca=;r`2pCwO{Ul66a6|~rXN`e=@uZX7m4c;J7l3ndx#z^ zl_hSH0gPcS&Hpnv@0_uB{p$_2UXp(t0U%jfLqkDhQsJ}(^xw);~)|CXBQwpG69#07-r zH~Gp*vx#Ii>)=>Z`Juver1uH= z#fJ!8a#i^)R(I02d*zSqW}>9g@_)4OBruPY9S5|Fr*iV#Szvnxr*72%Y4;WvG%A(o z@|Rpl1hS%TS1x4TGNQG8xyC7|6Z_hV^7f}(coC5MaF!_Nhj9sm(-8A4u2bGlAQQ$V zJ*!BhuEC`S?Lq1HKIeKYi9n2paXtC>h$?R2`kliMr_OL$&z=LK3fz$6?lk_xYb*mBIdif z)enL6)+*eZZFp|rac=EsAn1M6o7?gwWBEC5Ti`XK*(5xDciV}-1u8yst; z;1c=}-5sn5rh^FSBt=Y78uI_=uN9s5?Imh5Ns%$|A<;#N;@xiWAa1-Ovl-;(2!-Y5 z7eu!vDjc&K5)D|X7@>fIcj}3A9N`G0n{;-jz{AkA3C;@{Q~6%A9YUUwMxvlLrgfWFxT#g3EB zF!d}_>{fw<-PbDiSjH1I_bB#;u=~#yM;KRXURIpuvHpWYieI(J`!=T(0?9mX#ccyZ zd2+nscD@;geZ5oM3Ys%90IVN@(qwfF@xe3U9~KKyDrRn<9lJkgxps+3Jo^VSYk zuV>)wZ}U{Hty>WosmdLXeH67-6iOJNdj2~$x_C)V5#ZVq zkGjIA*r>flUF9Akbzq-5EU*GmW{|pVGz>b?NuAVc5t8d1WD(>jb;f>BWbk5j{|B9j zMlMum?rwl2{7G#&2tyw~QCl7Z;o-4ro8>+bTcdVg2hxK$b?z;1D!9w3^VIRMaIMbHmXpOg+_~wsT?h3s-N9LQ)->sopG4N2Ttd-s!~k%`@s< zuGuKalhvoaqfkH<>a!*|(CIz(<#~Zb1vAw*W+vlgFh+gnJPdlPt@?NGT$GPTchyhk zBZ)HlssH}28PPh0rsC?|7<%J1b$)z^vZB+3Ujg?zFVsXexPg!y(zFPIho?PZlf6%S_!9`U7BImbKv-WO+goCz%z|^ z`Xt0I<(OuE8#$o1XqHW`P4v{LS#t+udb(M&esUp_>5gW*ECvcUYrfnDjt_sX*~ccd zkjt9=^T#4IKWZ-Jog}JOUUOXr1+(gCo{YYVmUBz<yAqK9^SpI9W@eeM3Q>JSxH)n4k-5#z}u?d5Mkx_A1E(zQVQ z%QGNxElhj;{ycPXZ)feTPHQj@EYLn^QUxcuR@&bc*Dz^iX`e{pY5j}Ze`-nq?RS23 z0*L%2_wzsYHCo4MzlvSp`Gt;tL7n>mL6={2*D%biD!QXnttQ=ghryPJN+e> zK*o*X{)$dE948|ABmR|B*A4UUK0X^1d+wk8P8`vYqy9OOph8Z2|B*Y>FaZwspVSRF z?5*QJBN%F|%l&7b1!u?I@t@y$H|q2RQTC1XUl5PyXNK}E0)o9G+W4Pu?!pjrSd=X{ z`(JjtaTY1^zt-qCqRAnmocpW)jTIM&-1Gb&SNIBN33EWX{UM0QR8U$>qGd|AVaVsJNLPwE8rdq6&$Y zE(NhU!w~=?ICK|P8XDjFvTC#1wH77DmJy7 zu8s{B{m)okqf|4h*I`|Yds~Q>H`gWX0y05%T~aR~@gPZ;F}^AgxuCPIi3HvqbwhVQ zCfZa*H!>K8E1<7JL+gAw~|+p62# zy}cBZ_hsF-FJ@!J@1)yycPK22((Skl#vY5&U7Gxe=+6eaJ7WWgKAWw(cMb%p_EdMj z+Hu^U4;cRD3is#=>&en7osT!vujC*r<&v4~2OM9_$~Fdo0qR#(W}BL zc8sA{Yq1!NZ`RV8lE?s~Gs!`}!EiIaGe-uI4RpbY$mv+-8&B>eA;rtfn_Po3+;lm7JhKa$m;z|sj0Z)UUKC^-av+{lH zH0X_1r_p0GI0d2#c6nbzRJ@V-f7@jc^2S)k>Ot_!fmq&z!mJW=FQ!?X(*84yQiHVap!`auxw9K6ZRw*SdX zFQV~kX4wz|J79mARW<%)o6TxBm*v!JDAn?1JG?@$S@M;&!vc_;;0j}Sdlbxg>gL1d z-}+ryh8%Nf`_%Yu4c>&qzw{4&L4Rqwvs}}Ic8syi4!2K#m4Er4Yj(QLd2dqPlb=yo zvla_GBLJh06B<|{VO3Dfe%4)P1O}BhfZ9FmhD@7T?{?@dR=Y`WG~3-~Co6mIEVEv4 z{7rg;fu0k8W`T%$A*x2$V8@@QOj7yxun)02h9Pa7bHo`!5;TxRyZLaQM%{JRzXePq`u_-Z<_xYnet4lomvCpFTt7FQ&%jy*C zTG;}2r3i|s8U^OIp$5J!!-QV1AC_gd>unB$&@Vv{H>LnY(Pj>{+H(yyt4W_@aA&n? z!1StfC|$@j8^3sZ6>l^+!7z+LVRTaLzs-jm;+hD`!^&n@-wi7n;w-YPkFv6=FJ2Yt zv#oIg4A7@phnu7Ib(_XyI%9qQyKpBWuWmDa2bDR;orSANW?Dow>WJ-FBBbM!5tj8q zJ0q=cTmOzI!08T~*~wZkdkp^{C|QUJTW-ZSn{Tly*#Ht-4K@Ms;VFZo|G|I0pBtyK zd`{2LF}DUy^ukq=!zdKH)^#Hq>*I4BHV-R(t?M=uwz!cpw{d0a2{o=_r8kR}dVITT zdsmJXym-LQcZ8k6p9$F9#w$80c;d6tEgDT3gBcl8G?lzcsK%8rp2BfiK$kCfmGh_-zXknZa3>tE?u8 z*;HB;4Pd-j6B)lpPyuNGt_y$67x38^oEquHDaAmtu(>o^KcYaB?zEas(XX;aTL!4Y zCWaC5k`$EWs(kBDLi%H8lW*2xLq};)D#aP>_1$_$4zk&5$G|J(FjG*aG!R`amY1TC zN$ubw8_ewd(lKxG0~cz%#jOO&F5#az<+3(AsF*u!vS7fN@f$$!xmq{Hp%>>ol0$jemg&UXP~!Cixb#cGJmnqluRw|GP7=#Qd=-; zvDpJQdF%o+UkgY$B?xRRner{TG7{g-q2w?2;V_4@0?vk7R=!H}wsH@tK z2Sn>*HS~?nW}*@}uwN4>Vdt8%0WKMyS#q#eh(gZJJYppeebEp+n1O`qkIxk0GZLR& z;Y&i9ir=W0(}6=#mXL`0A?EyHh*GF4%i*z^*tswxu75^K|BT4~T@w;sZ8;zGiQ6A6 z>gBYeHnLnUT)s-Cl6=X(-KRQ#Bef!bX{bUdD6IHq;|RiXMl2|-=Cf?F3mL-BZ0z9I z1b-fc)C?*1V0OA-2S8SYa*CfRo&7x6(F&+o5oMte+9X!8*_540P{mWRnUJ0C6Zc@5}vBu-X_~O*#2maWOinZ)$a5n|D zECk{?j5RN2E;|Vrc}dkke^x~uFy9I1UHrohVH%$qjrkrM8?&vvS)e$ZIvBTDfkk=6 zM&h~Bh74y_M7(033<(FYDDk$mJeN&NaXljnOJLUeaL1=Rq=VI!V>9F<`nH_PdYn6O zF3vXCO)Mu-Hdv$LkM5{ctMeO7v-0;7EAjoDst9p7e0fr-UZM!4fXyqYMiz%NTj-0- zcYejjBa(#Th889%+r02=p=;9>xg?UW6&c8%+ESrOD1gWoXn;;phRT)LfZ$>SC#!g& zXfJ3!{N~Rp753cPg_BCc`OO~&^4C%;GeQQIoQlg3lK*L2O=U5?_|eY5>Q2Bck|_*u zcL#$+2+;%i?3z`1N!N=0E?9CCA+xg3@%N`!@A-94vut~u1n=+}JM+O)R`l2yk_$k@Zd!6J3F%HD+ zij^!A#1UDnCuIk`df9lP)r27?K*zY@a(JAm{&DR(fbl?*f3P*E@Y;^M67LJ+oKD|+ zM0kO|&xoc4`o1F~3RwLM5<6${tFdBUiv!FH&nooa&c(z3%_D}9oswRZFWDF858ruim0fB1h%42$qHEd;)?L_7A+DIYRC31SrJjtU_`V3 zWLH~5{wsX(Z)YE5!_*D^fa0|-@jGtly|2Lt2kT$+LW^Z3TI?q58%w}aJ4#M3>>QBy%2lxQb!Qy*azNZJ z0t@xRCL?gzDg7QPxEobwo1UPt4OKvVc_2RBTv*kj3@9ndRy*DkS?z8C6?P6vHsty4 zG%a9v(!9bk37pmE=@D-B8CftQO8BuS&LG(=oXrbHw0M0u1xz)<>B$W!N)JM^G*9;H zh|mfH{R{b^1$m&dX~CQF2n?#v_uW~iu;$KA!7bQJl-Irm_L0TU_ky>*BabitbIn5Y z`D}@#A)j=)YPd&uO{W*$+htQ>nWj-W&Xb9EZ*dq!ahe9#LXFwG3;Fw(gIfmUV9H+h znGioV4YN0WYzqs>Qg(}S;uZ-Ghdb2b79Wmgr&?Bv*!z(eA7^ZP#p8)~V-}9rKHbT_ zLFvWEUo^j1cd}r4vEiK?3hZWsIBS$8^VZATytq*@4#`%Vtr&;w;_dk3KW|S7!%13v zgUpIEn{QYLE;*~(1Y9~IR;;(#%EFtNog*BW^&Rm>* z{!Bbih2pC8U4$x$&6A-?Y;j@#i@EZc+AkT=gVF@WOunqRT#vL8l|9l*RGzrfBQ%J5 cz=M*P)w!iP=VOvqND%(nPa%b&S8J&M7rw&rcK`qY delta 4264 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 diff --git a/i18n/mayo_fr.ts b/i18n/mayo_fr.ts index 3a75a448..b32dfc39 100644 --- a/i18n/mayo_fr.ts +++ b/i18n/mayo_fr.ts @@ -58,12 +58,12 @@ Mayo::AppModule - + en Anglais - + fr Français @@ -555,66 +555,95 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::BRepMeasureError + Entity must be a vertex - L'entité doit être un sommet + L'entité doit être un sommet + Entity must be a circular edge - L'entité doit être une arête circulaire + L'entité doit être une arête circulaire + Entity must be a shape(BREP) - L'entité doit une forme BREP + L'entité doit une forme BREP + + Entity must be a geometric or polygon edge + L'entité doit être une arête avec une géométrie(courbe) ou discrétisée + + + + Entity must be a geometric or triangulation face + L'entité doit être une face avec une géométrie(surface) ou triangulée + + + Computation of minimum distance failed - Échec du calcul de la distance minimum + Échec du calcul de la distance minimum + + Unable to find center of the shape + Impossible de trouver le centre de la forme + + + All entities must be edges - Toutes les entités doivent être des arêtes + Toutes les entités doivent être des arêtes + Entity must be a linear edge - L'entité doit une arête linéaire + L'entité doit une arête linéaire + All entities must be faces - Toutes les entités doivent être des faces + Toutes les entités doivent être des faces + Entities must not be parallel - Les entités ne doivent pas être parallèles + Les entités ne doivent pas être parallèles + + + + Bounding box computed is void + La boîte englobante calculée est vide + Unknown error - Erreur inconnue + Erreur inconnue Mayo::CliExport - + Mesh BRep shapes Maillage des formes BRep - + Imported Importé - + Exported {} Export de {} terminé - + Importing... Import en cours ... - + Exporting {}... Export de {} en cours ... @@ -908,12 +937,12 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Document suivant - + System Information... Informations du système ... - + Copy to Clipboard Copier dans le presse-papiers @@ -937,40 +966,40 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera À propos - + Mayo By Fougue Ltd. Mayo par EURL Fougue - + Version %1 (%2bit) - + Built on %1 at %2 Compilée le %1 à %2 - + Qt %1 - + OpenCascade %1 - - gmio %1 - - - - + %1 By %2 %1 par %2 + + + %1 %2 + %1 %2 + Mayo::DialogInspectXde @@ -980,77 +1009,87 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera XDE - - ShapeType=%1, Evolution=%2 - - - - + Yes Oui - + No Non - + File Size: %1<br>Dimensions: %2x%3 Depth: %4 - + Taille fichier : %1<br>Dimensions: %2x%3 Profondeur : %4 - + Error when loading texture file(invalid path?) - + Erreur lors du chargement du fichier de texture (chemin incorrect ?) + + + + %1,offset:%2 + %1,offset:%2 + + + + <data> + <données> - + Shape Forme - + Color - Couleur + Color - + Material - + Material - + VisMaterial - + VisMatériau - + Dimension - + Dimension - + Datum - + Datum - + GeomTolerance - + GeomTolerance - + Error Erreur - + + ShapeType=%1, ShapeLocation=%2, Evolution=%3 + + + + This document is not suitable for XDE Ce document n'est pas XDE-compatible - + Attributes Attributs @@ -1102,7 +1141,7 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera - + Error Erreur @@ -1117,17 +1156,21 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera '%1' ne dispose pas des permissions de lecture - + + Error when writing to '%1' + Erreur lors de l'écriture de '%1' + + Error when writing to'%1' - Erreur lors de l'écriture vers '%1' + Erreur lors de l'écriture vers '%1' - + Restore values for default section only Restaurer les valeurs seulement pour la section par défaut - + Restore values for the whole group Restaure les valeurs pour tout le groupe @@ -1261,37 +1304,37 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::GraphicsMeshObjectDriver - + Mesh_Wireframe [Maillage] Filaire - + Mesh_Shaded [Maillage] Ombré - + Mesh_Shrink [Maillage] Rétréci - + color Couleur - + edgeColor Couleur des arêtes - + showEdges Montrer les arêtes - + showNodes Montrer les nœuds @@ -1349,35 +1392,55 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::GraphicsShapeObjectDriver - + Shape_Wireframe [Forme] Filaire - + Shape_HiddenLineRemoval [Forme] Suppression des arêtes cachées - + Shape_Shaded [Forme] Ombré - + Shape_ShadedWithFaceBoundary [Forme] Ombré avec arêtes + + Mayo::IO::AssimpReaderI18N + + + LINE primitives not supported yet + Les primitives LINE ne sont actuellement pas prises en charge + + + + Some primitive not supported + Certaines primitive ne sont pas prises en charge + + + + Texture not found: {} +Tried: + Texture introuvable : {} +Essai : + + Mayo::IO::DxfReader::Properties - + Scale entities according some factor Redimensionner les entités selon un facteur d'échelle - + Import text/dimension objects Importer les entités texte et dimension @@ -1386,32 +1449,32 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Grouper toutes les entités d'une même couche dans une forme compound BREP - + Group all objects within a layer into a single compound shape Grouper toutes les entités d'une même couche dans une forme compound BREP - + Name of the font to be used when creating shape for text objects Nom de la police de caractères à utiliser lors de la création des formes correspondantes aux entités TEXT - + scaling Mise à l'échelle - + importAnnotations Import des annotations - + groupLayers Grouper les entitiés par couche - + fontNameForTextObjects Police de caractères pour le texte @@ -1483,27 +1546,27 @@ Seulement applicable si l'option `%1` est activée Nombre maximal de chiffres significatifs lors de l'écriture de valeurs de type `double` - + float64Format Format nombres flottants 64bit - + float64Precision Précision nombres flottants 64bit - + createZipArchive Créer une archive ZIP - + zipEntryFilename Nom du fichier de l'entrée ZIP - + useZip64 Utiliser les extensions ZIP64 @@ -1742,17 +1805,17 @@ Seulement applicable si l'option `%1` est activée Convertisseur de coordonnées - + transformationFormat Format de transformation - + format Format cible - + forceExportUV Forcer l'export UV @@ -1761,52 +1824,52 @@ Seulement applicable si l'option `%1` est activée Transformation des coordonnées d'OpenCascade vers glTF - + Source coordinate system transformation Transformation du système de coordonnées source - + Target coordinate system transformation Transformation du système de coordonnées cible - + Preferred transformation format for writing into glTF file Transformation préférée pour l'écriture des fichiers glTF - + Export UV coordinates even if there is no mapped texture Exporter les coordonnées UV même si aucune texture mappée - + Automatically choose most compact representation between Mat4 and TRS Choisir automatiquement la représentation la plus compacte entre Mat4 et TRS - + 4x4 transformation matrix Mactrice de transformation 4x4 - + Transformation decomposed into Translation vector, Rotation quaternion and Scale factor(T * R * S) Transformation décomposée en vecteur de translation, quaternion de rotation et facteur d'échelle (T x R x S) - + Name format for exporting nodes Format du nom utilisé pour exporter la hiérarchie de nœuds - + Name format for exporting meshes Format du nom utilisé pour exporter la hiérarchie de maillages - + Write image textures into target file. If set to `false` then texture images will be written as separate files. @@ -1831,7 +1894,7 @@ If set to `false` then texture images will be written as separate files. Applicable only if option `{}` is set to `{}` - + Merge faces within a single part. May reduce JSON size thanks to smaller number of primitive arrays @@ -1840,7 +1903,7 @@ May reduce JSON size thanks to smaller number of primitive arrays Peut réduire la taille JSON grâce à une quantité réduite de tableaux de primitives - + Prefer keeping 16-bit indexes while merging face. May reduce binary data size thanks to smaller triangle indexes. @@ -1853,42 +1916,42 @@ Peut réduite la taille des données grâce à une quantité réduite d'ind Applicable seulement si l'option `{}` est cochée - + inputCoordinateSystem Système de coordonnées d'entrée - + outputCoordinateSystem Système de coordonnées de sortie - + nodeNameFormat Format du nom pour les nœuds - + meshNameFormat Format du nom pour les maillages - + embedTextures Incorporer les textures dans le même fichier cible - + mergeFaces Fusionner les faces - + keepIndices16b Utiliser des indices 16bit - + Option supported from OpenCascade ≥ v7.6 [option={}, actual version={}] Option prise en charge à partir de OpenCascade ≥ v7.6 [option={}, version actuelle={}] @@ -1906,22 +1969,22 @@ Applicable seulement si l'option `{}` est cochée Mayo::IO::OccIgesReader::Properties - + bsplineContinuity Continuité BSpline - + surfaceCurveMode Mode des courbes - + readFaultyEntities Lire les entités en erreur - + readOnlyVisibleEntities Lire seulement les entités visibles @@ -1931,7 +1994,7 @@ Applicable seulement si l'option `{}` est cochée - + Preference for the computation of curves in case of 2D/3D inconsistency in an entity which has both 2D and 3D representations. Concerned entity types are 141 (Boundary), 142 (CurveOnSurface) and 508 (Loop). These are entities representing a contour lying on a surface, which is translated to a TopoDS_Wire, formed by TopoDS_Edges. Each TopoDS_Edge must have a 3D curve and a 2D curve that reference the surface. @@ -1942,47 +2005,47 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b - + Read failed entities Lecture des entités en erreur - + Curves are taken as they are in the IGES file. C0 entities of Open CASCADE may be produced - + If an IGES BSpline, Spline or CopiousData curve is C0 continuous, it is broken down into pieces of C1 continuous Geom_BSplineCurve - + IGES Spline curves are broken down into pieces of C2 continuity. If C2 cannot be ensured, the Spline curves will be broken down into pieces of C1 continuity - + Use the preference flag value in the entity's `Parameter Data` section - + The 2D is used to rebuild the 3D in case of their inconsistency - + The 2D is always used to rebuild the 3D (even if 3D is present in the file) - + The 3D is used to rebuild the 2D in case of their inconsistency - + The 3D is always used to rebuild the 2D (even if 2D is present in the file) @@ -1990,32 +2053,32 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Mayo::IO::OccIgesWriter::Properties - + brepMode Mode BRep - + planeMode Mode des plans - + lengthUnit Unité de longueur - + Indicates if planes should be saved as Bsplines or Planes (type 108). Writing p-curves on planes is disabled - + OpenCascade TopoDS_Faces will be translated into IGES 144 (Trimmed Surface) entities, no BRep entities will be written to the IGES file - + OpenCascade TopoDS_Faces will be translated into IGES 510 (Face) entities, the IGES file will contain BRep entities @@ -2040,22 +2103,22 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Convertisseur de coordonnées - + Source coordinate system transformation Transformation du système de coordonnées source - + Target coordinate system transformation Transformation du système de coordonnées cible - + inputCoordinateSystem Système de coordonnées d'entrée - + outputCoordinateSystem Système de coordonnées de sortie @@ -2063,114 +2126,114 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Mayo::IO::OccStepReader::Properties - + productContext Context du produit - + assemblyLevel Niveau assemblage - + preferredShapeRepresentation Représentation des formes préférée - + readShapeAspect Lire l'aspect des formes - + readSubShapesNames Lire le nom des sous-formes - + encoding Encodage - + When reading AP 209 STEP files, allows selecting either only `design` or `analysis`, or both types of products for translation Note that in AP 203 and AP214 files all products should be marked as `design`, so if this mode is set to `analysis`, nothing will be read - + Specifies which data should be read for the products found in the STEP file - + Specifies preferred type of representation of the shape of the product, in case if a STEP file contains more than one representation (i.e. multiple `PRODUCT_DEFINITION_SHAPE` entities) for a single product - + Defines whether shapes associated with the `PRODUCT_DEFINITION_SHAPE` entity of the product via `SHAPE_ASPECT` should be translated. This kind of association was used for the representation of hybrid models (i.e. models whose shape is composed of different types of representations) in AP 203 files before 1998, but it is also used to associate auxiliary information with the sub-shapes of the part. Though STEP translator tries to recognize such cases correctly, this parameter may be useful to avoid unconditionally translation of shapes associated via `SHAPE_ASPECT` entities. - + Indicates whether to read sub-shape names from 'Name' attributes of STEP Representation Items - + Translate only products that have `PRODUCT_DEFINITION_CONTEXT` with field `life_cycle_stage` set to `design` - + Translate only products that have `PRODUCT_DEFINITION_CONTEXT` with field `life_cycle_stage` set to `analysis` - + Translates all products - + Translate the assembly structure and shapes associated with parts only(not with sub-assemblies) - + Translate only the assembly structure without shapes(a structure of empty compounds). This mode can be useful as an intermediate step in applications requiring specialized processing of assembly parts - + Translate only shapes associated with the product, ignoring the assembly structure (if any). This can be useful to translate only a shape associated with specific product, as a complement to assembly mode - + Translate both the assembly structure and all associated shapes. If both shape and sub-assemblies are associated with the same product, all of them are read and put in a single compound - + Translate all representations(if more than one, put in compound) - + Shift Japanese Industrial Standards - + EUC(Extended Unix Code), multi-byte encoding primarily for Japanese, Korean, and simplified Chinese - + GB(Guobiao) encoding for Simplified Chinese @@ -2178,108 +2241,108 @@ This kind of association was used for the representation of hybrid models (i.e. Mayo::IO::OccStepWriter::Properties - + schema Schéma - + lengthUnit Unité de longueur - + assemblyMode Mode de l'assemblage - + freeVertexMode Mode des sommets libres - + writeParametericCurves Écrire les courbes paramétriques - + writeSubShapesNames Écrire le nom des sous-formes - + headerAuthor Auteur (en-tête) - + headerOrganization Organisation (en-tête) - + headerOriginatingSystem Système source (en-tête) - + headerDescription Description (en-tête) - + Version of schema used for the output STEP file Version du schéma à utiliser pour le fichier STEP de sortie - + Defines a unit in which the STEP file should be written. If set to unit other than millimeter, the model is converted to these units during the translation - + Parameter to write all free vertices in one SDR (name and style of vertex are lost) or each vertex in its own SDR (name and style of vertex are exported) - + All free vertices are united into one compound and exported in one shape definition representation (vertex name and style are lost) - + Each vertex is exported in its own `SHAPE DEFINITION REPRESENTATION`(vertex name and style are not lost, but the STEP file size increases) - + Indicates whether parametric curves (curves in parametric space of surface) should be written into the STEP file. It can be disabled in order to minimize the size of the resulting file. - + Indicates whether to write sub-shape names to 'Name' attributes of STEP Representation Items - + Author attribute in STEP header - + Organization(of author) attribute in STEP header - + Originating system attribute in STEP header - + Description attribute in STEP header @@ -2302,13 +2365,13 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::IO::OccStlWriterI18N - + targetFormat Format cible - - + + Not all BRep faces are meshed Les faces BRep ne sont pas toutes maillées @@ -2326,11 +2389,53 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::IO::OccVrmlWriter::Properties - + shapeRepresentation Représentation des formes + + Mayo::IO::OffReaderI18N + + + Can't open input file + Impossible d'ouvrir le fichier d'entrée + + + + + Unexpected end of file + Fin de fichier inattendu + + + + Wrong header keyword(should be [C][N][4]OFF + Mot-clé d'entête incorrect (attendu [C][N][4]OFF) + + + + No vertex or face count + Aucun nombre de sommets ou de facets + + + + No vertex coordinates at current line + Aucunes coordonnées de sommet à la ligne actuelle + + + + Inconsistent vertex count of face + + + + + Mayo::IO::OffWriterI18N + + + Failed to open file + Impossible d'ouvrir le fichier + + Mayo::IO::PlyWriterI18N @@ -2397,24 +2502,24 @@ It can be disabled in order to minimize the size of the resulting file. %2 - + Reading file Lecture fichier - + Unknown format Format inconnu - + Error during import of '{}' {} Erreur pendant l'import de '{}' {} - + No supporting reader Aucun lecteur compatible @@ -2430,12 +2535,12 @@ It can be disabled in order to minimize the size of the resulting file. - + File transfer problem Problème de transfert fichier - + Error during export to '{}' {} Erreur pendant l'export de '{}' @@ -2448,22 +2553,22 @@ It can be disabled in order to minimize the size of the resulting file. %2 - + No supporting writer Aucun writer compatible - + Transfer Transfert - + Write Écriture - + File write problem Problème d'écriture fichier @@ -2475,67 +2580,74 @@ It can be disabled in order to minimize the size of the resulting file. Mayo, une visionneuse 3D en code libre basée surQt5/OpenCascade - + Theme for the UI(classic|dark) Thème de l'IHM (classic|dark) - + name nom - + + Writes log messages into output file Écrit les messages de log dans un fichier de sortie - + + Don't filter out debug log messages in release build Ne pas filtrer les messages de debug dans la version "release" - Disable progress reporting in console output(CLI-mode only) - Désactiver l'indicateur de progression dans la sortie console (mode CLI seulement) + Désactiver l'indicateur de progression dans la sortie console (mode CLI seulement) - + + Show detailed system information and quit Montrer les informations détaillées du système puis quitter - + + files files - + Files to open at startup, optionally Fichiers à ouvrir au démarrage, optionnel - + + [files...] [fichiers ...] - + Execute unit tests and exit application Exécuter les tests unitaires et quitter l'application - + + OpenCascade settings file doesn't exist or is not readable [path=%1] Le fichier de configuration OpenCascade n'existe pas ou non lisible [chemin=%1] - + + OpenCascade settings file could not be loaded with QSettings [path=%1] Le fichier de configuration OpenCascade n'a pu être chargé par QSettings [chemin=%1] - + + Failed to load translation file [path=%1] Échec chargement du fichier de traductions [chemin=%1] @@ -2548,7 +2660,8 @@ It can be disabled in order to minimize the size of the resulting file. Export de {} en cours ... - + + Failed to load application settings file [path=%1] Échec chargement du fichier de configuration [chemin=%1] @@ -2559,27 +2672,75 @@ It can be disabled in order to minimize the size of the resulting file. - + Settings file(INI format) to load at startup Fichier de configuration (format INI) à charger au démarrage - + Mayo the opensource 3D CAD viewer and converter Mayo le visualiseur et convertisseur 3D pour la CAO - - - + + + + + + filepath - + + mayo-conv the opensource CAD converter + mayo-conv le convertisseur CAO opensource + + + + Display help on commandline options + Affichage de l'aide des options en ligne de commande + + + + Display version information + Affichage de l'information de version + + + + Use settings file(INI format) for the conversion. When this option isn't specified then cached settings are used + Utiliser le fichier de paramètres (format INI) pour la conversion. Quand cette option n'est pas spécifiée alors les paramètres sauvegardés sont utilisés + + + + Cache settings file provided with --use-settings for further use + Sauvegarder le fichier de paramètres fourni via --use-settings pour une utilisation ultérieure + + + + Write settings cache to an output file(INI format) + Écrire les paramètres sauvegardés dans un fichier de sortie (format INI) + + + Export opened files into an output file, can be repeated for different formats(eg. -e file.stp -e file.igs...) Exporter des fichiers dans un fichier de sortie, répétable selon les différents formats supportés (par exemple -e file.stp -e file.igs ...) + + + Disable progress reporting in console output + Désactiver indication du progrès dans la sortie console + + + + Files to open(import) + Fichiers à ouvrir (import) + + + + Error when writing to '%1' + Erreur lors de l'écriture vers '%1' + Mesh BRep shapes Maillage des formes BRep @@ -2601,12 +2762,12 @@ It can be disabled in order to minimize the size of the resulting file. Export de %1 en cours ... - + No input files -> nothing to export Auncun fichier en entrée -> aucun export - + Failed to load theme '%1' Impossible de charger le thème '%1' @@ -2797,13 +2958,13 @@ It can be disabled in order to minimize the size of the resulting file. Selectionner fichier pièce - + Warning Avertissement - - + + Error Erreur @@ -2871,24 +3032,59 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::MeasureDisplayI18N + Sum - Total + Total + + + + (<font color="#FF5500">X</font>{0} <font color="#55FF00">Y</font>{1} <font color="#0077FF">Z</font>{2}){3} + + + + + X{0} Y{1} Z{2} + + Diameter: {0}{1} - Diamètre: {0}{1} + Diamètre: {0}{1} + + + + Ø{0} + + + + + Angle: {0}{1} + + + + + + {0}: {1}{2} + + + + + Min point: {0}<br>Max point: {1}<br>Size: {2} x {3} x {4}{5}<br>Volume: {6}{7} + Min point : {0}<br>Max point : {1}<br>Dimensions : {2} x {3} x {4}{5}<br>Volume : {6}{7} Min Distance: {0}{1}<br>Point1: {2}<br>Point2: {3} Distance Min: {0}{1}<br>Point1: {2}<br>Point2: {3} + Length - Longueur + Longueur + Area - Aire + Aire @@ -3310,67 +3506,67 @@ Modifié le: %3 {1 Mayo::WidgetGuiDocument - + Fit All Adapter à tout - + Edit Grid Éditer la grille - + Edit clip planes Éditer les plans de coupe - + Explode assemblies Éclater l'assemblage - + Measure shapes Mesures - + Isometric Isométrique - + Back Arrière - + Front Devant - + Left Gauche - + Right Droit - + Top Haut - + Bottom Bas - + <b>Left-click</b>: popup menu of pre-defined views <b>CTRL+Left-click</b>: apply '%1' view <b>Click gauche</b> : menu déroulant des vues pré-définies @@ -3380,12 +3576,12 @@ Modifié le: %3 {1 Mayo::WidgetHomeFiles - + New Document Nouveau Document - + Create and add an empty document where you can import files @@ -3394,12 +3590,12 @@ Create and add an empty document where you can import files Créer et ajouter un document vide où importer des fichiers - + Open Document(s) Ouvrir des Documents - + Select files to load and open as distinct documents @@ -3408,28 +3604,28 @@ Select files to load and open as distinct documents Selectionnez les fichiers à charger et ouvrir comme documents distincts - + today %1 aujourd'hui %1 - + yersterday %1 hier %1 - - + + %1 %2 %1 %2 - + %1 days ago %2 %1 jours %2 - + %1 Size: %2 @@ -3535,122 +3731,172 @@ Lu: %5 Unité aire - + Measure Mesure - + Millimeter(mm) Millimètre (mm) - + Centimeter(cm) Centimètre (cm) - + Meter(m) Mètre (m) - + Inch(in) Pouce (in) - + Foot(ft) Pied (ft) - + Yard(yd) Yard (yd) - + Degree(°) Degré (°) - + Radian(rad) Radian (rad) - + + Volume Unit + Unité volume + + + Vertex Position Position sommet - + Circle Center Centre cercle - + Circle Diameter Diamètre cercle - + Min Distance Distance min - + Center-to-center Distance Distance entre les centres - + Length Longueur - + + Bounding Box + Boîte englobante + + + + Cubic Millimeter(mm³) + Millimètre cube (mm³) + + + + Cubic Centimeter(cm³) + Centimètre cube (cm³) + + + + Cubic Meter(m³) + Mètre cube (m³) + + + + Cubic Inch(in³) + Pouce cube (in³) + + + + Cubic Foot(ft³) + Pied cube (ft³) + + + + Liter(L) + Litre (L) + + + + Imperial Gallon(GBgal) + Gallon impérial (GBgal) + + + + US Gallon(USgal) + Gallon US (USgal) + + + Square Millimeter(mm²) Millimètre carré (mm²) - + Square Centimeter(cm²) Centimètre carré (cm²) - + Square Meter(m²) Mètre carré (m²) - + Square Inch(in²) Pouce carré (in²) - + Square Foot(ft²) Pied carré (ft²) - + Square Yard(yd²) Yard carré (yd²) - + Angle - + Surface Area Aire surface - + Length Unit Unité longueur @@ -3675,12 +3921,12 @@ Lu: %5 Pied - + Angle Unit Unité angle - + Select entities to measure Sélectionner les entités à mesurer @@ -3698,7 +3944,7 @@ Lu: %5 Retirer du document - + <unnamed> <sans nom> @@ -3711,7 +3957,7 @@ Lu: %5 Format des noms d'instance - + Show {} Montrer {} From c6d36f4c1a152e88d6b9397c5e3cd00b141033dd Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 21 Mar 2024 18:52:15 +0100 Subject: [PATCH 039/101] App: introduce reusable LibraryInfo{Array} --- src/app/command_system_information.cpp | 34 +++----------------- src/app/commands_help.cpp | 3 +- src/app/commands_help.h | 14 -------- src/app/dialog_options.cpp | 2 +- src/app/library_info.cpp | 44 ++++++++++++++++++++++++++ src/app/library_info.h | 32 +++++++++++++++++++ src/app/main.cpp | 5 +-- 7 files changed, 86 insertions(+), 48 deletions(-) create mode 100644 src/app/library_info.cpp create mode 100644 src/app/library_info.h diff --git a/src/app/command_system_information.cpp b/src/app/command_system_information.cpp index 03525e97..c2ed221e 100644 --- a/src/app/command_system_information.cpp +++ b/src/app/command_system_information.cpp @@ -8,6 +8,7 @@ #include "app_module.h" #include "command_system_information_occopengl.h" +#include "library_info.h" #include "qtwidgets_utils.h" #include "../base/meta_enum.h" #include "../base/filepath.h" @@ -123,12 +124,6 @@ QTextStream& operator<<(QTextStream& str, std::string_view sv) const char indent[] = " "; const char indentx2[] = " "; -std::vector& getLibraryInfos() -{ - static std::vector vec; - return vec; -} - // Helper function returning unicode representation of a QChar object in the form "U+NNNN" QString toUnicodeHexa(const QChar& ch) { @@ -210,9 +205,9 @@ QString CommandSystemInformation::data() ostr << '\n' << "OpenCascade: " << OCC_VERSION_STRING_EXT << " (build)" << '\n'; // Other registered libraries - for (const LibraryInfo& libInfo : getLibraryInfos()) { - ostr << '\n' << to_QString(libInfo.name) << ": " << to_QString(libInfo.version) - << " " << to_QString(libInfo.versionDetails) + for (const auto& libInfo : LibraryInfoArray::get()) { + ostr << '\n' << libInfo.name << ": " << libInfo.version + << " " << libInfo.versionDetails << '\n'; } @@ -414,25 +409,4 @@ QString CommandSystemInformation::data() return strSysInfo; } -void CommandSystemInformation::addLibraryInfo( - std::string_view libName, - std::string_view version, - std::string_view versionDetails - ) -{ - if (!libName.empty() && !version.empty()) { - const LibraryInfo libInfo{ - std::string(libName), - std::string(version), - std::string(versionDetails) - }; - getLibraryInfos().push_back(std::move(libInfo)); - } -} - -Span CommandSystemInformation::libraryInfos() -{ - return getLibraryInfos(); -} - } // namespace Mayo diff --git a/src/app/commands_help.cpp b/src/app/commands_help.cpp index 47430bca..8bc866cc 100644 --- a/src/app/commands_help.cpp +++ b/src/app/commands_help.cpp @@ -7,6 +7,7 @@ #include "commands_help.h" #include "dialog_about.h" +#include "library_info.h" #include "qtwidgets_utils.h" #include "../qtcommon/qstring_conv.h" @@ -41,7 +42,7 @@ CommandAbout::CommandAbout(IAppContext* context) void CommandAbout::execute() { auto dlg = new DialogAbout(this->widgetMain()); - for (const auto& libInfo : CommandSystemInformation::libraryInfos()) + for (const auto& libInfo : LibraryInfoArray::get()) dlg->addLibraryInfo(libInfo.name, libInfo.version); QtWidgetsUtils::asyncDialogExec(dlg); diff --git a/src/app/commands_help.h b/src/app/commands_help.h index 23c49a92..e0e64159 100644 --- a/src/app/commands_help.h +++ b/src/app/commands_help.h @@ -7,7 +7,6 @@ #pragma once #include "commands_api.h" -#include "../base/span.h" namespace Mayo { @@ -25,19 +24,6 @@ class CommandSystemInformation : public Command { void execute() override; static QString data(); - - struct LibraryInfo { - std::string name; - std::string version; - std::string versionDetails; - }; - static void addLibraryInfo( - std::string_view libName, - std::string_view version, - std::string_view versionDetails = "" - ); - static Span libraryInfos(); - static constexpr std::string_view Name = "system-info"; }; diff --git a/src/app/dialog_options.cpp b/src/app/dialog_options.cpp index 8fd8de92..ad31d7d2 100644 --- a/src/app/dialog_options.cpp +++ b/src/app/dialog_options.cpp @@ -381,7 +381,7 @@ void DialogOptions::saveAs() m_settings->saveAs(&fileSettings, &AppModule::excludeSettingPredicate); fileSettings.sync(); if (fileSettings.get().status() != QSettings::NoError) - QtWidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), tr("Error when writing to'%1'").arg(filepath)); + QtWidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), tr("Error when writing to '%1'").arg(filepath)); } void DialogOptions::cancelChanges() diff --git a/src/app/library_info.cpp b/src/app/library_info.cpp new file mode 100644 index 00000000..ac0c7ff7 --- /dev/null +++ b/src/app/library_info.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "library_info.h" + +#include + +namespace Mayo { + +namespace { + +std::vector& getLibraryInfos() +{ + static std::vector vec; + return vec; +} + +} // namespace + +void LibraryInfoArray::add( + std::string_view libName, + std::string_view version, + std::string_view versionDetails + ) +{ + if (!libName.empty() && !version.empty()) { + const LibraryInfo libInfo{ + std::string(libName), + std::string(version), + std::string(versionDetails) + }; + getLibraryInfos().push_back(std::move(libInfo)); + } +} + +Span LibraryInfoArray::get() +{ + return getLibraryInfos(); +} + +} // namespace Mayo diff --git a/src/app/library_info.h b/src/app/library_info.h new file mode 100644 index 00000000..8126eb80 --- /dev/null +++ b/src/app/library_info.h @@ -0,0 +1,32 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/span.h" + +#include +#include + +namespace Mayo { + +struct LibraryInfo { + std::string name; + std::string version; + std::string versionDetails; +}; + +struct LibraryInfoArray { + static void add( + std::string_view libName, + std::string_view version, + std::string_view versionDetails = "" + ); + + static Span get(); +}; + +} // namespace Mayo diff --git a/src/app/main.cpp b/src/app/main.cpp index 8d10a26d..282defa8 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -31,6 +31,7 @@ #include "app_module.h" #include "commands_help.h" #include "document_tree_node_properties_providers.h" +#include "library_info.h" #include "mainwindow.h" #include "qtgui_utils.h" #include "theme.h" @@ -397,10 +398,10 @@ static int runApp(QCoreApplication* qtApp) appModule->properties()->retranslate(); // Register library infos - CommandSystemInformation::addLibraryInfo( + LibraryInfoArray::add( IO::AssimpLib::strName(), IO::AssimpLib::strVersion(), IO::AssimpLib::strVersionDetails() ); - CommandSystemInformation::addLibraryInfo( + LibraryInfoArray::add( IO::GmioLib::strName(), IO::GmioLib::strVersion(), IO::GmioLib::strVersionDetails() ); From 8f2db012cdba4d3aceef51903b4cfcb7bbddd392 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 21 Mar 2024 18:53:05 +0100 Subject: [PATCH 040/101] MayoConv: handle relevant command-line options --- CMakeLists.txt | 3 + src/app/app_module_properties.cpp | 7 +- src/app/app_module_properties.h | 2 + src/app/iwidget_main_page.h | 2 +- src/base/typed_scalar.h | 4 + src/cli/main.cpp | 260 ++++++++++++++++++++++++------ 6 files changed, 222 insertions(+), 56 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c85e417..d23db60e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -622,6 +622,7 @@ list( APPEND MayoConv_HeaderFiles ${PROJECT_SOURCE_DIR}/src/app/app_module_properties.h ${PROJECT_SOURCE_DIR}/src/app/app_module.h + ${PROJECT_SOURCE_DIR}/src/app/library_info.h ${PROJECT_SOURCE_DIR}/src/app/recent_files.h ) @@ -635,6 +636,7 @@ list( APPEND MayoConv_SourceFiles ${PROJECT_SOURCE_DIR}/src/app/app_module_properties.cpp ${PROJECT_SOURCE_DIR}/src/app/app_module.cpp + ${PROJECT_SOURCE_DIR}/src/app/library_info.cpp ${PROJECT_SOURCE_DIR}/src/app/recent_files.cpp ) @@ -643,6 +645,7 @@ if(Mayo_BuildConvCli) mayo-conv ${MayoConv_HeaderFiles} ${MayoConv_SourceFiles} + i18n/mayo.qrc i18n/qt${QT_VERSION_MAJOR}base.qrc ${Mayo_RcIconsWin} ${Mayo_AppIconMacOS} diff --git a/src/app/app_module_properties.cpp b/src/app/app_module_properties.cpp index 1374fc66..43274bf2 100644 --- a/src/app/app_module_properties.cpp +++ b/src/app/app_module_properties.cpp @@ -20,13 +20,12 @@ AppModuleProperties::AppModuleProperties(Settings* settings) : PropertyGroup(settings), groupId_system(settings->addGroup(textId("system"))), groupId_application(settings->addGroup(textId("application"))), + groupId_meshing(settings->addGroup(textId("meshing"))), + groupId_graphics(settings->addGroup(textId("graphics"))), language(this, textId("language"), &AppModule::languages()), m_settings(settings) { - const auto groupId_meshing = settings->addGroup(textId("meshing")); - const auto groupId_graphics = settings->addGroup(textId("graphics")); - - const auto sectionId_systemUnits = settings->addSection(this->groupId_system, textId("units")); + const auto sectionId_systemUnits = settings->addSection(groupId_system, textId("units")); const auto sectionId_graphicsClipPlanes = settings->addSection(groupId_graphics, textId("clipPlanes")); const auto sectionId_graphicsMeshDefaults = settings->addSection(groupId_graphics, textId("meshDefaults")); diff --git a/src/app/app_module_properties.h b/src/app/app_module_properties.h index 8440c7f7..2961e69e 100644 --- a/src/app/app_module_properties.h +++ b/src/app/app_module_properties.h @@ -54,12 +54,14 @@ class AppModuleProperties : public PropertyGroup { PropertyBool linkWithDocumentSelector{ this, textId("linkWithDocumentSelector") }; PropertyBool forceOpenGlFallbackWidget{ this, textId("forceOpenGlFallbackWidget") }; // Meshing + const Settings::GroupIndex groupId_meshing; enum class BRepMeshQuality { VeryCoarse, Coarse, Normal, Precise, VeryPrecise, UserDefined }; PropertyEnum meshingQuality{ this, textId("meshingQuality") }; PropertyLength meshingChordalDeflection{ this, textId("meshingChordalDeflection") }; PropertyAngle meshingAngularDeflection{ this, textId("meshingAngularDeflection") }; PropertyBool meshingRelative{ this, textId("meshingRelative") }; // Graphics + const Settings::GroupIndex groupId_graphics; PropertyEnum navigationStyle{ this, textId("navigationStyle") }; PropertyBool defaultShowOriginTrihedron{ this, textId("defaultShowOriginTrihedron") }; PropertyDouble instantZoomFactor{ this, textId("instantZoomFactor") }; diff --git a/src/app/iwidget_main_page.h b/src/app/iwidget_main_page.h index 7987fda4..52523f32 100644 --- a/src/app/iwidget_main_page.h +++ b/src/app/iwidget_main_page.h @@ -13,7 +13,7 @@ namespace Mayo { class CommandContainer; // Provides an interface for main pages within the Mayo application -// As its core, Mayo UI is basically a stack of widgets +// At its core, Mayo UI is basically a stack of widgets // Such widgets are called "main pages" or just "pages" and only a single one is active at a time class IWidgetMainPage : public QWidget { Q_OBJECT diff --git a/src/base/typed_scalar.h b/src/base/typed_scalar.h index ecb0017b..92cf03e4 100644 --- a/src/base/typed_scalar.h +++ b/src/base/typed_scalar.h @@ -25,6 +25,10 @@ class TypedScalar { return m_scalar == other.m_scalar; } + bool operator!=(const TypedScalar& other) const { + return m_scalar != other.m_scalar; + } + private: Scalar m_scalar; }; diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 725a9369..1ee9aa6d 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -5,6 +5,7 @@ ****************************************************************************/ #include "../app/app_module.h" +#include "../app/library_info.h" #include "../base/application.h" #include "../base/io_system.h" #include "../base/settings.h" @@ -35,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +48,7 @@ #include #include #include +#include namespace Mayo { @@ -62,15 +65,109 @@ namespace { // Stores arguments(options) passed at command line struct CommandLineArguments { - FilePath filepathSettings; + FilePath filepathUseSettings; + FilePath filepathWriteSettings; FilePath filepathLog; - bool includeDebugLogs = true; std::vector listFilepathToExport; std::vector listFilepathToOpen; - bool cliProgressReport = true; + bool cacheUseSettings = false; + bool includeDebugLogs = true; + bool progressReport = true; bool showSystemInformation = false; }; +// Helper to filter out AppModule settings that are not useful for MayoConv application +class ExludeSettingPredicate { +public: + ExludeSettingPredicate() + { + auto settings = AppModule::get()->settings(); + auto properties = AppModule::get()->properties(); + const int groupCount = settings->groupCount(); + for (int igroup = 0; igroup < groupCount; ++igroup) { + const Settings::GroupIndex groupId{igroup}; + if ( + groupId != properties->groupId_system + && groupId != properties->groupId_application + && groupId != properties->groupId_graphics + ) + { + continue; // Skip + } + + for (int isection = 0; isection < settings->sectionCount(groupId); ++isection) { + const Settings::SectionIndex sectionId{groupId, isection}; + for (int isetting = 0; isetting < settings->settingCount(sectionId); ++isetting) { + const Settings::SettingIndex settingId{sectionId, isetting}; + m_setExcludedProperty.insert(settings->property(settingId)); + } + } + } + } + + bool fn(const Property& prop) const + { + return AppModule::excludeSettingPredicate(prop) + || m_setExcludedProperty.find(&prop) != m_setExcludedProperty.cend(); + } + +private: + std::unordered_set m_setExcludedProperty; +}; + +std::ostream& operator<<(std::ostream& ostr, const QString& str) +{ + ostr << to_stdString(str); + return ostr; +} + +void showSystemInformation(std::ostream& ostr) +{ + const char indent[] = " "; + + // Mayo version + ostr << '\n' + << "Mayo: v" << strVersion + << " commit:" << strVersionCommitId + << " revnum:" << versionRevisionNumber + << " " << QT_POINTER_SIZE * 8 << "bit" + << '\n'; + + // OS version + ostr << '\n' << "OS: " << QSysInfo::prettyProductName() + << " [" << QSysInfo::kernelType() << " version " << QSysInfo::kernelVersion() << "]" << '\n' + << "Current CPU Architecture: " << QSysInfo::currentCpuArchitecture() << '\n'; + + // Qt version + ostr << '\n' << QLibraryInfo::build() << '\n'; + + // OpenCascade version + ostr << '\n' << "OpenCascade: " << OCC_VERSION_STRING_EXT << " (build)" << '\n'; + + // Other registered libraries + for (const LibraryInfo& libInfo : LibraryInfoArray::get()) { + ostr << '\n' << libInfo.name << ": " << libInfo.version + << " " << libInfo.versionDetails + << '\n'; + } + + // I/O supported formats + { + ostr << '\n' << "Import(read) formats:" << '\n' << indent; + const IO::System* ioSystem = AppModule::get()->ioSystem(); + for (IO::Format format : ioSystem->readerFormats()) + ostr << IO::formatIdentifier(format) << " "; + + ostr << '\n' << "Export(write) formats:" << '\n' << indent; + for (IO::Format format : ioSystem->writerFormats()) + ostr << IO::formatIdentifier(format) << " "; + + ostr << '\n'; + } + + ostr.flush(); +} + } // namespace // Parses command line and process Qt builtin options(basically --version and --help) @@ -81,17 +178,41 @@ static CommandLineArguments processCommandLine() // Configure command-line parser QCommandLineParser cmdParser; cmdParser.setApplicationDescription( - Main::tr("Mayo the opensource 3D CAD viewer and converter") + Main::tr("mayo-conv the opensource CAD converter") ); - cmdParser.addHelpOption(); - cmdParser.addVersionOption(); - const QCommandLineOption cmdFileSettings( - QStringList{ "s", "settings" }, - Main::tr("Settings file(INI format) to load at startup"), - Main::tr("filepath") + const QCommandLineOption cmdShowHelp( + QStringList{ "?", "h", "help" }, + Main::tr("Display help on commandline options") + ); + cmdParser.addOption(cmdShowHelp); + + const QCommandLineOption cmdShowVersion( + QStringList{ "v", "version" }, + Main::tr("Display version information") ); - cmdParser.addOption(cmdFileSettings); + cmdParser.addOption(cmdShowVersion); + + const QCommandLineOption cmdUseSettings( + QStringList{ "u", "use-settings" }, + Main::tr("Use settings file(INI format) for the conversion. When this option isn't specified " + "then cached settings are used"), + Main::tr("filepath") + ); + cmdParser.addOption(cmdUseSettings); + + const QCommandLineOption cmdCacheSettings( + QStringList{ "c", "cache-settings" }, + Main::tr("Cache settings file provided with --use-settings for further use") + ); + cmdParser.addOption(cmdCacheSettings); + + const QCommandLineOption cmdWriteSettingsCache( + QStringList{ "w", "write-settings-cache" }, + Main::tr("Write settings cache to an output file(INI format)"), + Main::tr("filepath") + ); + cmdParser.addOption(cmdWriteSettingsCache); const QCommandLineOption cmdFileToExport( QStringList{ "e", "export" }, @@ -101,12 +222,12 @@ static CommandLineArguments processCommandLine() ); cmdParser.addOption(cmdFileToExport); - const QCommandLineOption cmdFileLog( + const QCommandLineOption cmdLogFile( QStringList{ "log-file" }, Main::tr("Writes log messages into output file"), Main::tr("filepath") ); - cmdParser.addOption(cmdFileLog); + cmdParser.addOption(cmdLogFile); const QCommandLineOption cmdDebugLogs( QStringList{ "debug-logs" }, @@ -114,11 +235,11 @@ static CommandLineArguments processCommandLine() ); cmdParser.addOption(cmdDebugLogs); - const QCommandLineOption cmdCliNoProgress( + const QCommandLineOption cmdNoProgress( QStringList{ "no-progress" }, - Main::tr("Disable progress reporting in console output(CLI-mode only)") + Main::tr("Disable progress reporting in console output") ); - cmdParser.addOption(cmdCliNoProgress); + cmdParser.addOption(cmdNoProgress); const QCommandLineOption cmdSysInfo( QStringList{ "system-info" }, @@ -128,18 +249,29 @@ static CommandLineArguments processCommandLine() cmdParser.addPositionalArgument( Main::tr("files"), - Main::tr("Files to open at startup, optionally"), + Main::tr("Files to open(import)"), Main::tr("[files...]") ); cmdParser.process(QCoreApplication::arguments()); // Retrieve arguments - if (cmdParser.isSet(cmdFileSettings)) - args.filepathSettings = filepathFrom(cmdParser.value(cmdFileSettings)); + if (cmdParser.isSet(cmdShowHelp)) + cmdParser.showHelp(); + + if (cmdParser.isSet(cmdShowVersion)) + cmdParser.showVersion(); - if (cmdParser.isSet(cmdFileLog)) - args.filepathLog = filepathFrom(cmdParser.value(cmdFileLog)); + if (cmdParser.isSet(cmdUseSettings)) + args.filepathUseSettings = filepathFrom(cmdParser.value(cmdUseSettings)); + + if (cmdParser.isSet(cmdWriteSettingsCache)) + args.filepathWriteSettings = filepathFrom(cmdParser.value(cmdWriteSettingsCache)); + + args.cacheUseSettings = cmdParser.isSet(cmdCacheSettings); + + if (cmdParser.isSet(cmdLogFile)) + args.filepathLog = filepathFrom(cmdParser.value(cmdLogFile)); if (cmdParser.isSet(cmdFileToExport)) { for (const QString& strFilepath : cmdParser.values(cmdFileToExport)) @@ -153,7 +285,7 @@ static CommandLineArguments processCommandLine() // By default this will exclude debug logs in release build args.includeDebugLogs = cmdParser.isSet(cmdDebugLogs); #endif - args.cliProgressReport = !cmdParser.isSet(cmdCliNoProgress); + args.progressReport = !cmdParser.isSet(cmdNoProgress); args.showSystemInformation = cmdParser.isSet(cmdSysInfo); return args; @@ -207,6 +339,7 @@ static void initGui(GuiApplication* guiApp) if (!guiApp) return; + guiApp->setAutomaticDocumentMapping(false); // GuiDocument objects aren't needed setFunctionCreateGraphicsDriver([]() -> Handle_Graphic3d_GraphicDriver { return new OpenGl_GraphicDriver(GraphicsUtils::AspectDisplayConnection_create()); }); @@ -215,17 +348,16 @@ static void initGui(GuiApplication* guiApp) guiApp->addGraphicsObjectDriver(std::make_unique()); } -bool cliExcludingSettingPredicate(const Property& prop) -{ - return AppModule::excludeSettingPredicate(prop) - || prop.dynTypeName() == PropertyRecentFiles::TypeName; -} - // Initializes and runs Mayo application static int runApp(QCoreApplication* qtApp) { const CommandLineArguments args = processCommandLine(); + const ExludeSettingPredicate excludeSettingPredicate; + auto fnExcludeSettingPredicate = [&](const Property& prop) { + return excludeSettingPredicate.fn(prop); + }; + // Helper function: print critical message and exit application with code failure auto fnCriticalExit = [](const QString& msg) { qCritical().noquote() << msg; @@ -235,16 +367,16 @@ static int runApp(QCoreApplication* qtApp) // Helper function: load application settings from INI file(if provided) otherwise use the // application regular storage(eg registry on Windows) auto fnLoadAppSettings = [&](Settings* appSettings) { - if (args.filepathSettings.empty()) { + if (args.filepathUseSettings.empty()) { appSettings->load(); } else { - const QString strFilepathSettings = filepathTo(args.filepathSettings); - if (!filepathIsRegularFile(args.filepathSettings)) + const QString strFilepathSettings = filepathTo(args.filepathUseSettings); + if (!filepathIsRegularFile(args.filepathUseSettings)) fnCriticalExit(Main::tr("Failed to load application settings file [path=%1]").arg(strFilepathSettings)); QSettingsStorage fileSettings(strFilepathSettings, QSettings::IniFormat); - appSettings->loadFrom(fileSettings, &cliExcludingSettingPredicate); + appSettings->loadFrom(fileSettings, fnExcludeSettingPredicate); } }; @@ -275,7 +407,9 @@ static int runApp(QCoreApplication* qtApp) // Initialize Base application auto app = Application::instance().get(); app->addTranslator(&qtAppTranslate); // Set Qt i18n backend +#ifdef MAYO_OS_WINDOWS initOpenCascadeEnvironment("opencascade.conf"); +#endif // Initialize Gui application auto guiApp = new GuiApplication(app); @@ -297,41 +431,66 @@ static int runApp(QCoreApplication* qtApp) appModule->properties()->IO_bindParameters(ioSystem); appModule->properties()->retranslate(); -#if 0 + // Application settings + appModule->settings()->resetAll(); + fnLoadAppSettings(appModule->settings()); + + // Write cached settings to ouput file if asked by user + if (!args.filepathWriteSettings.empty()) { + const QString strFilepathSettings = filepathTo(args.filepathWriteSettings); + QSettingsStorage fileSettings(strFilepathSettings, QSettings::IniFormat); + appModule->settings()->saveAs(&fileSettings, fnExcludeSettingPredicate); + fileSettings.sync(); + if (fileSettings.get().status() != QSettings::NoError) + fnCriticalExit(Main::tr("Error when writing to '%1'").arg(strFilepathSettings)); + + qInfo().noquote() << Main::tr("Settings cache written to %1").arg(strFilepathSettings); + return 0; + } + // Register library infos - CommandSystemInformation::addLibraryInfo( + LibraryInfoArray::add( IO::AssimpLib::strName(), IO::AssimpLib::strVersion(), IO::AssimpLib::strVersionDetails() ); - CommandSystemInformation::addLibraryInfo( + LibraryInfoArray::add( IO::GmioLib::strName(), IO::GmioLib::strVersion(), IO::GmioLib::strVersionDetails() ); // Process CLI - if (args.showSystemInformation) { - CommandSystemInformation cmdSysInfo(nullptr); - cmdSysInfo.execute(); - return qtApp->exec(); + if (args.showSystemInformation) { + showSystemInformation(std::cout); + return EXIT_SUCCESS; } -#endif - - if (!args.listFilepathToExport.empty()) { - if (args.listFilepathToOpen.empty()) - fnCriticalExit(Main::tr("No input files -> nothing to export")); - guiApp->setAutomaticDocumentMapping(false); // GuiDocument objects aren't needed - appModule->settings()->resetAll(); - fnLoadAppSettings(appModule->settings()); + int exitCode = EXIT_SUCCESS; + if (args.listFilepathToOpen.empty()) { + if (!args.listFilepathToExport.empty()) { + qCritical() << Main::tr("No input files -> nothing to export"); + exitCode = EXIT_FAILURE; + } + } + else { QTimer::singleShot(0, qtApp, [=]{ CliExportArgs cliArgs; - cliArgs.progressReport = args.cliProgressReport; + cliArgs.progressReport = args.progressReport; cliArgs.filesToOpen = args.listFilepathToOpen; cliArgs.filesToExport = args.listFilepathToExport; cli_asyncExportDocuments(app, cliArgs, [=](int retcode) { qtApp->exit(retcode); }); }); - return qtApp->exec(); + exitCode = qtApp->exec(); + } + + if (args.cacheUseSettings) { + if (!args.filepathUseSettings.empty()) { + appModule->settings()->save(); + qInfo().noquote() << Main::tr("Settings '%1' cached").arg(filepathTo(args.filepathUseSettings)); + } + else { + qWarning().noquote() << Main::tr("No supplied settings to cache"); + } } - return 0; + return exitCode; } } // namespace Mayo @@ -346,6 +505,5 @@ int main(int argc, char* argv[]) QCoreApplication::setApplicationName("MayoConv"); QCoreApplication::setApplicationVersion(QString::fromUtf8(Mayo::strVersion)); QCoreApplication app(argc, argv); - // TODO Read settings from Mayo application return Mayo::runApp(&app); } From 4d9f49126493d1f1a5b6b388d10235718e28ca0d Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 22 Mar 2024 12:46:42 +0100 Subject: [PATCH 041/101] App: function renamings in AppModule --- src/app/app_module.cpp | 12 ++++++------ src/app/app_module.h | 6 +++--- src/app/main.cpp | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/app/app_module.cpp b/src/app/app_module.cpp index 60bc3f71..602d6767 100644 --- a/src/app/app_module.cpp +++ b/src/app/app_module.cpp @@ -242,7 +242,7 @@ const RecentFile* AppModule::findRecentFile(const FilePath& fp) const return itFound != listRecentFile.cend() ? &(*itFound) : nullptr; } -void AppModule::recordRecentFileThumbnail(GuiDocument* guiDoc) +void AppModule::recordRecentFile(GuiDocument* guiDoc) { if (!guiDoc) return; @@ -263,7 +263,7 @@ void AppModule::recordRecentFileThumbnail(GuiDocument* guiDoc) return; RecentFile newRecentFile = *recentFile; - const bool okRecord = this->impl_recordRecentFileThumbnail(&newRecentFile, guiDoc); + const bool okRecord = this->impl_recordRecentFile(&newRecentFile, guiDoc); if (!okRecord) return; @@ -274,7 +274,7 @@ void AppModule::recordRecentFileThumbnail(GuiDocument* guiDoc) m_props.recentFiles.setValue(newListRecentFile); } -void AppModule::recordRecentFileThumbnails(GuiApplication* guiApp) +void AppModule::recordRecentFiles(GuiApplication* guiApp) { if (!guiApp) return; @@ -287,7 +287,7 @@ void AppModule::recordRecentFileThumbnails(GuiApplication* guiApp) continue; // Skip RecentFile newRecentFile = *recentFile; - if (this->impl_recordRecentFileThumbnail(&newRecentFile, guiDoc)) { + if (this->impl_recordRecentFile(&newRecentFile, guiDoc)) { auto indexRecentFile = std::distance(&listRecentFile.front(), recentFile); newListRecentFile.at(indexRecentFile) = newRecentFile; } @@ -309,7 +309,7 @@ void AppModule::readRecentFiles(QDataStream& stream, RecentFiles* recentFiles) qDebug() << fmt::format( "QDataStream error\n Function: {}\n Status: {}", Q_FUNC_INFO, MetaEnum::name(status) - ).c_str(); + ).c_str(); return false; } @@ -419,7 +419,7 @@ AppModule::~AppModule() m_settings = nullptr; } -bool AppModule::impl_recordRecentFileThumbnail(RecentFile* recentFile, GuiDocument* guiDoc) +bool AppModule::impl_recordRecentFile(RecentFile* recentFile, GuiDocument* guiDoc) { if (!recentFile) return false; diff --git a/src/app/app_module.h b/src/app/app_module.h index c9572de8..2fc4c1b8 100644 --- a/src/app/app_module.h +++ b/src/app/app_module.h @@ -83,8 +83,8 @@ class AppModule : // Recent files void prependRecentFile(const FilePath& fp); const RecentFile* findRecentFile(const FilePath& fp) const; - void recordRecentFileThumbnail(GuiDocument* guiDoc); - void recordRecentFileThumbnails(GuiApplication* guiApp); + void recordRecentFile(GuiDocument* guiDoc); + void recordRecentFiles(GuiApplication* guiApp); QSize recentFileThumbnailSize() const { return { 190, 150 }; } void setRecentFileThumbnailRecorder(std::function fn); static void readRecentFiles(QDataStream& stream, RecentFiles* recentFiles); @@ -119,7 +119,7 @@ class AppModule : AppModule(const AppModule&) = delete; // Not copyable AppModule& operator=(const AppModule&) = delete; // Not copyable - bool impl_recordRecentFileThumbnail(RecentFile* recentFile, GuiDocument* guiDoc); + bool impl_recordRecentFile(RecentFile* recentFile, GuiDocument* guiDoc); Settings* m_settings = nullptr; IO::System m_ioSystem; diff --git a/src/app/main.cpp b/src/app/main.cpp index 282defa8..b08d860c 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -413,7 +413,7 @@ static int runApp(QCoreApplication* qtApp) } // Record recent files when documents are closed - guiApp->signalGuiDocumentErased.connectSlot(&AppModule::recordRecentFileThumbnail, AppModule::get()); + guiApp->signalGuiDocumentErased.connectSlot(&AppModule::recordRecentFile, AppModule::get()); // Register WidgetModelTreeBuilter prototypes WidgetModelTree::addPrototypeBuilder(std::make_unique()); @@ -444,7 +444,7 @@ static int runApp(QCoreApplication* qtApp) appModule->settings()->resetAll(); fnLoadAppSettings(appModule->settings()); const int code = qtApp->exec(); - appModule->recordRecentFileThumbnails(guiApp); + appModule->recordRecentFiles(guiApp); appModule->settings()->save(); return code; } From af28397b47b06abf19045b6f343179bd1d04a8aa Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 22 Mar 2024 13:14:34 +0100 Subject: [PATCH 042/101] Doc: update screencast for CLI --- doc/screencast_cli.gif | Bin 47097 -> 56953 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/screencast_cli.gif b/doc/screencast_cli.gif index a61f90b004b68f28981c0d9fe1f6bab831c54eb5..8f3132802ea0ac44eb9e93a790361c3d3d796e30 100644 GIT binary patch delta 34406 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 IXhHaY0R)e5(f|Me delta 26976 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 w=sn+a From 04e8d1ddc4483a60948e534b85db0a3474a286c4 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 25 Mar 2024 10:42:23 +0100 Subject: [PATCH 043/101] Fix some Codacy issues --- src/app/widget_model_tree_builder_xde.cpp | 2 +- src/base/property_enumeration.cpp | 2 +- src/base/property_enumeration.h | 2 +- src/base/property_value_conversion.cpp | 2 +- src/io_dxf/dxf.cpp | 14 ++----- src/io_dxf/dxf.h | 49 ++++++++++++----------- src/io_dxf/io_dxf.cpp | 2 +- src/measure/measure_display.cpp | 4 +- 8 files changed, 36 insertions(+), 41 deletions(-) diff --git a/src/app/widget_model_tree_builder_xde.cpp b/src/app/widget_model_tree_builder_xde.cpp index 0320a763..8dfa9c72 100644 --- a/src/app/widget_model_tree_builder_xde.cpp +++ b/src/app/widget_model_tree_builder_xde.cpp @@ -214,7 +214,7 @@ QTreeWidgetItem* WidgetModelTreeBuilder_Xde::buildXdeTree( QByteArray WidgetModelTreeBuilder_Xde::instanceNameFormat() const { - return QtCoreUtils::QByteArray_frowRawData(Module::get()->instanceNameFormat.name()); + return QtCoreUtils::QByteArray_frowRawData(Module::get()->instanceNameFormat.valueName()); } void WidgetModelTreeBuilder_Xde::setInstanceNameFormat(const QByteArray& format) diff --git a/src/base/property_enumeration.cpp b/src/base/property_enumeration.cpp index 27131804..556c3e8e 100644 --- a/src/base/property_enumeration.cpp +++ b/src/base/property_enumeration.cpp @@ -55,7 +55,7 @@ void PropertyEnumeration::clearDescriptions() m_vecDescription.clear(); } -std::string_view PropertyEnumeration::name() const +std::string_view PropertyEnumeration::valueName() const { return m_enumeration ? m_enumeration->findNameByValue(m_value) : std::string_view{}; } diff --git a/src/base/property_enumeration.h b/src/base/property_enumeration.h index fad122a7..3b59ee53 100644 --- a/src/base/property_enumeration.h +++ b/src/base/property_enumeration.h @@ -34,7 +34,7 @@ class PropertyEnumeration : public Property { std::string_view findDescription(Enumeration::Value value) const; void clearDescriptions(); - std::string_view name() const; + std::string_view valueName() const; Enumeration::Value value() const; bool setValue(Enumeration::Value value); bool setValueByName(std::string_view name); diff --git a/src/base/property_value_conversion.cpp b/src/base/property_value_conversion.cpp index e44ae8c4..9f8f6e71 100644 --- a/src/base/property_value_conversion.cpp +++ b/src/base/property_value_conversion.cpp @@ -138,7 +138,7 @@ PropertyValueConversion::Variant PropertyValueConversion::toVariant(const Proper return TKernelUtils::colorToHex(constRef(prop)); } else if (isType(prop)) { - return std::string(constRef(prop).name()); + return std::string(constRef(prop).valueName()); } else if (isType(prop)) { const auto& qtyProp = constRef(prop); diff --git a/src/io_dxf/dxf.cpp b/src/io_dxf/dxf.cpp index d65ed947..deb7c639 100644 --- a/src/io_dxf/dxf.cpp +++ b/src/io_dxf/dxf.cpp @@ -144,6 +144,7 @@ Base::Vector3d toVector3d(const double* a) return result; } +#if 0 CDxfWrite::CDxfWrite(const char* filepath) : m_ofs(filepath, std::ios::out), //TODO: these should probably be parameters in config file @@ -1889,20 +1890,11 @@ void CDxfWrite::writeObjectsSection() std::string fileSpec = m_dataDir + ss.str(); m_ofs << getPlateFile(fileSpec); } +#endif CDxfRead::CDxfRead(const char* filepath) : m_ifs(filepath) { - // start the file - m_fail = false; - m_ColorIndex = 0; - m_eUnits = eMillimeters; - m_measurement_inch = false; - m_layer_name = "0"; // Default layer name - m_ignore_errors = true; - - m_version = RUnknown; - if (!m_ifs) m_fail = true; else @@ -3568,7 +3560,7 @@ void CDxfRead::ReportError_readInteger(const char* context) msg += "Failed to read integer from '"; msg += m_str; msg += "'"; - this->ReportError(msg.c_str()); + this->ReportError(msg); } std::streamsize CDxfRead::gcount() const diff --git a/src/io_dxf/dxf.h b/src/io_dxf/dxf.h index 8c52ab02..a4b43878 100644 --- a/src/io_dxf/dxf.h +++ b/src/io_dxf/dxf.h @@ -358,6 +358,24 @@ struct Dxf_SPLINE { std::vector fitPoints; }; +typedef enum +{ + RUnknown, + ROlder, + R10, + R11_12, + R13, + R14, + R2000, + R2004, + R2007, + R2010, + R2013, + R2018, + RNewer, +} eDXFVersion_t; + +#if 0 //*************************** //data structures for writing //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project @@ -397,22 +415,6 @@ struct LWPolyDataOut std::vector Bulge; point3D Extr; }; -typedef enum -{ - RUnknown, - ROlder, - R10, - R11_12, - R13, - R14, - R2000, - R2004, - R2007, - R2010, - R2013, - R2018, - RNewer, -} eDXFVersion_t; //******************** class CDxfWrite @@ -584,6 +586,7 @@ class CDxfWrite void makeBlockRecordTableBody(); void makeBlockSectionHead(); }; +#endif namespace DxfPrivate { @@ -612,15 +615,15 @@ class CDxfRead private: std::ifstream m_ifs; - bool m_fail; + bool m_fail = false; std::string m_str; std::string m_unused_line; - eDxfUnits_t m_eUnits; - bool m_measurement_inch; - std::string m_layer_name; + eDxfUnits_t m_eUnits = eMillimeters; + bool m_measurement_inch = false; + std::string m_layer_name{"0"}; // Default layer name std::string m_section_name; std::string m_block_name; - bool m_ignore_errors; + bool m_ignore_errors = true; std::streamsize m_gcount = 0; int m_line_nb = 0; @@ -711,8 +714,8 @@ class CDxfRead void ReportError_readInteger(const char* context); protected: - ColorIndex_t m_ColorIndex; - eDXFVersion_t m_version; // Version from $ACADVER variable in DXF + ColorIndex_t m_ColorIndex = 0; + eDXFVersion_t m_version = RUnknown; // Version from $ACADVER variable in DXF std::streamsize gcount() const; virtual void get_line(); diff --git a/src/io_dxf/io_dxf.cpp b/src/io_dxf/io_dxf.cpp index b01f5e69..c911d79e 100644 --- a/src/io_dxf/io_dxf.cpp +++ b/src/io_dxf/io_dxf.cpp @@ -313,7 +313,7 @@ void DxfReader::applyProperties(const PropertyGroup* group) m_params.scaling = ptr->scaling; m_params.importAnnotations = ptr->importAnnotations; m_params.groupLayers = ptr->groupLayers; - m_params.fontNameForTextObjects = ptr->fontNameForTextObjects.name(); + m_params.fontNameForTextObjects = ptr->fontNameForTextObjects.valueName(); } } diff --git a/src/measure/measure_display.cpp b/src/measure/measure_display.cpp index c0c12e15..6ce4e81e 100644 --- a/src/measure/measure_display.cpp +++ b/src/measure/measure_display.cpp @@ -462,12 +462,12 @@ void MeasureDisplayArea::sumAdd(const IMeasureDisplay& other) MeasureDisplayBoundingBox::MeasureDisplayBoundingBox(const MeasureBoundingBox& bnd) : m_bnd(bnd), + m_gfxMinPoint(new AIS_Point(new Geom_CartesianPoint(bnd.cornerMin))), + m_gfxMaxPoint(new AIS_Point(new Geom_CartesianPoint(bnd.cornerMax))), m_gfxXLengthText(new AIS_TextLabel), m_gfxYLengthText(new AIS_TextLabel), m_gfxZLengthText(new AIS_TextLabel) { - m_gfxMinPoint = new AIS_Point(new Geom_CartesianPoint(bnd.cornerMin)); - m_gfxMaxPoint = new AIS_Point(new Geom_CartesianPoint(bnd.cornerMax)); const TopoDS_Shape shapeBox = BRepPrimAPI_MakeBox(bnd.cornerMin, bnd.cornerMax); auto aisShapeBox = new AIS_Shape(shapeBox); From 03b944fe02b79be002e3ac21bdeb394810f4eaf7 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 29 Mar 2024 13:16:08 +0100 Subject: [PATCH 044/101] Add option to reload Document on file change Relates to GitHub #153 --- i18n/mayo_en.qm | Bin 56609 -> 57395 bytes i18n/mayo_en.ts | 277 ++++++++++++++++------------ i18n/mayo_fr.qm | Bin 69040 -> 70052 bytes i18n/mayo_fr.ts | 283 +++++++++++++++++------------ src/app/app_module_properties.cpp | 39 ++-- src/app/app_module_properties.h | 1 + src/app/commands_file.cpp | 87 +++++---- src/app/commands_file.h | 12 +- src/app/document_files_watcher.cpp | 117 ++++++++++++ src/app/document_files_watcher.h | 53 ++++++ src/app/widget_main_control.cpp | 69 ++++++- src/app/widget_main_control.h | 14 +- 12 files changed, 665 insertions(+), 287 deletions(-) create mode 100644 src/app/document_files_watcher.cpp create mode 100644 src/app/document_files_watcher.h diff --git a/i18n/mayo_en.qm b/i18n/mayo_en.qm index 20c191e56c7ca4eb49ebd2adc49a1d63c454cc08..e8c1cf9cd5c6d5a1d653abcc711fec9327449a40 100644 GIT binary patch delta 5700 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 R2?^SGL;SuCU8tSZ{XfCA&!GSS delta 5173 zcmX9?dq9qN8@}H6d7k%tpkiq>R2z{c64um4=`?eU9EMKhG(@ZKQ*RETa!N`$)ugSH zLkcO1bn=bm7;S804s9|+tjL>vW(&w#8hfMh`&LHrzW9Bjs{U}6lQK1$36vRjEqfUo_D zr-4IfiD&p+K)lH3C&b$jaJW}WR(F$&1n4gDm{7ouN4U5G{npY90E2pQI~!oIfo2{D zCf5VK@0cG7FenUS$oCh)QYvAAnh)p!wH-)&F z297ji&OS39EHmR_e={D?z zK9IhafNvHah~Gya?|k6+0oc#y zcb?_w=R!)~?`_7yTsVGv9T?&P$4d@C`an3gIuipia7i@ZCz>&BB!=r5%Ju8uUF!?H zI~QY3={4La20r7s@NhJIGXDV*!p*qxDSS6Fo;88+8cMq=Le{lFR}7Ib>9D>kERBo?}NhAJJ4<#Zq|63gZv1X7fUx<^Nb+F{lB5}?}) z#CGoQJsNT6KI8WeSnKjDFyai>cc%gCO&v(8>r3_igw0;e?1~US~mCX8p=HsJbzVA`8IT3xj~Ig*dm2>n4xKg#czp)ID6< z^9LDThRgp+045wnQ!2GGTtv%}dw{8TuK7oi4z0zhK!kvQ7E(O^-ZH!r0k`*M`fR?R z^N-A;h^3`^nauJr#j_(;*7FHH$&N8&UY@MiF$XfJlG%3Ux|lGT+XAi|(^uw}N`p5Z zHDls@+3<#Ez<=k-+&BNm{15h!c?y<5T(&H5*7y8FrEEqx4=8?57B-tA>3vle*0ztO z@}exZgCex9l9@t=0`F~-t?6|hz(d*Q`741QHnOc}`!hSn%FZUKfw^9??{c35;VrTs z;%kBUOR|UHj_m) z@mqNp?*w}Mz1$WGQd=zVGlu65ijjXYf-&C~CtvmnsXUx3k2c;1R;S7T6Y_u_&ydHa zcmU2O`TF<>R@4*njh5@3{4UJ*Wo`5Z5=?i&OgTQ9E}^f5)(RbH>4VRm!m7v99OFl`qUNu<^4=>9?=Q@~WW|al7eX?qUfwdim9M~%)EumrKB_X+<>!v;zcC_X)4TlSv1*Cq} zV`1OaEu?s-kaeAgCq5GjyvFgIAB7`LL<=9GtcW5kxg?lsuYbXUaa1_}ryJ1qrSScM zD^%rB;pSm_W*II#j7z1u_X*GRZB*?^;rWX(6xkl3Eu@r%#7bzN)Xd)bsqp$CJx{3< z-rnNDpKKNF8YRHhLhL`pm;3Y-9qnm&Mx*GML<2s!E4oC|fbjk%F=*a$V9G-=*pV?h zRwd3qK+ohE;(`(P0H3E~R2C1ixh%$X5lP(*G3L8t?9&nA+LtU;+bhIGrv@O`Nlg6k zJeg?}6P^D@d?Rl2$z<=aG-JYkGbTEU=@qkBS3VIlC(}>|(;zW3oR-$4iMt+=x)P(f z@9IyCX|0%*?#lPq#oWX+K(<=UE!Y4=Cy3ufF+{c9#G*oOldI^gTdaW;Avq7=eqjlca|=T;b>+Wk!h&YWd%+}%Yl7qxT}g_g zB)=GX5|t(eytd-(<0y@5W{gd5rHR%&F!c{p%9nNDmqv*c3jOevc=X zhDmKR1X5`ub+k~ed)gI()e5TmeTC#5M3EX4dLaR*+NrRvYvZsOp|HgTz`jl4-a;nx z*DL($YgrM0SIn66oLw_PF?&26bNEXUJcPJqnj-wSlfcoHiZ4w&9C@&)_^*No_DM6N z(`hq0pD^R0EsEG1qK*yn8b}4pbyxj$w~jqe!jgY}ogLVn?yafyP6z zb5Kv#jTS}LANjzMSVdmp_k3@mD2-%jdc`U#t_=Z(NeWZ7imD&fuBZv8N49N>TD#6a zOi?s;ip|hVapi6zuxzZNDU}PSSSfzjG9>mx75|FWTTrylWz2It6s<8~bfDFYV+s_n zv*G}4gHo6k2~Hczf5mI;tF$U6qxOrG{R*~I8$l-i$l;G&%7Nu_4wV+lVVzw}(WLa< zdY^Mas&cx24GT|%a;|l!1`a8MCo!f&t|~)YWEXGW~+?$=YdNa zm8<&Fkri*0t0r<-ceYfn`oW7O_k}V!yOyGztlZSNv$Hp}DmQf;1-jfXd%;{)Q4+EL8&&Cp^Hl3-Rr&5zR>oJVsx1aKk)x{G(XPO}RMnH_=S<&& zs;3+H{QwWui&3wDcbZi%Z5SfQ4ArYr8YDZbdi_ZxdyS>)&0pN7pj8c*&CLI`K5C1D z+{htJ{m!3^^{#sLAWIA8cY@k;Bn{fXL>(|i#rPQV0L-%XECH4!=NyPo7nWx08CmvFb(PPsv=Fdc}hh;Nv~&s7DjY;C6Mi+C))U z_feY$%xAOFs<(}zm?pkw#)uqshBLdOyMsEbfU&K+q250?j3bz>`e3Ig>wZ%oc4Z!C zC98|Y$!tn8b>(tC7tc~xMW(U&%u+X*;#o0m)i*=wSpP58cQP%3fH~@iDW9>aeW`wI zx&7c-4~^-oh-RjbqbAm$hIG+u)`qO0rxuzmiHxz+I?XN*ks+C{*`L%GsC=x+e@xL; zjnEV&l`xOK^ff?}sCgjp zypR)`j@9?rZ5Nv~9qlnx?T4Db7mTKd_F84&c1}REwVI{$DDAD*DW9H?{#olXAe9Bm zNjprxjcK z;xO&;!IL?X-PM){F%7Luh1!PG_G~KeX@5F@9awx>`-}fr&U%H~J7=lhK9y!1vs8Qc zH7PsPQ~Tg)CMTWk+DAV5jA?i6GpBcWeNWc@rMS=b{8HN?Cj;+zYTx#f@iMwxhpS}l zbcRlU`3%$hsLsltjO0JkS^EVs|D6JKBR{9OymEA-8fl@8o6ggkwY|ntHzOq!D38+3 z4ih+p#_N{k(DHXS>dcc3a1)%@huXk-5$EN?cBIem~Lanr%coBx}=1ctfU)t z+g>dJgsr+=uTJqw7NN`9v7d%I=n9j%asID*qAT)0&seDoLjSr5L|b%2ydU)RfiNn=3?*DHJ$(UZP<<a{U=PoNUqFYXgKL7(kK zMl?U@_g^JL-PY+(I}Kqabk^4`=*=-)rN1>SlJ&reXyT9F{q%R1ui*IHq`yDl59WWI z85hX(4-fpr(0J%{Fvtu%iYh8Z2M(`BB2((S#(=40==d$;jD#L!>b7xy=s6Cd zp~i^(p$uV9A zlGMgDYcxJyYv5>g-}q;99NTY<@u|%fQa{}Iy6GARm@3kB-d3QR4DiAd%)=r?AQC0s pR)Q+^7e}eYsn--;iN>)#Hua;thLUCZAvPruHJ-AP+qHo<{|6FF6nX#v diff --git a/i18n/mayo_en.ts b/i18n/mayo_en.ts index 7eb010c7..2d6133e3 100644 --- a/i18n/mayo_en.ts +++ b/i18n/mayo_en.ts @@ -211,7 +211,7 @@ Mayo::AppModuleProperties - + language Language @@ -226,52 +226,59 @@ Application - + meshing BRep Meshing - + graphics Graphics - + units Units - + clipPlanes Clip planes - + meshDefaults Mesh Defaults - + import Import - + export Export - + Language used for the application. Change will take effect after application restart - + + Monitors the file system for changes to documents opened in the application + +When such a file change is detected then the application proposes to reload(open again) the document + + + + In case where multiple documents are opened, make sure the document displayed in the 3D view corresponds to what is selected in the model tree - + Force usage of the fallback Qt widget to display OpenGL graphics. When `OFF` the application will try to use OpenGL framebuffer for rendering, this allows to display overlay widgets(eg measure tools panel) with translucid background. However using OpenGL framebuffer might cause troubles for some users(eg empty 3D window) especially on macOS. @@ -282,49 +289,49 @@ This option is applicable when OpenCascade ≥ 7.6 version. Change will take eff - + Controls precision of the mesh to be computed from the BRep shape - + For the tessellation of faces the chordal deflection limits the distance between a curve and its tessellation - + For the tessellation of faces the angular deflection limits the angle between subsequent segments in a polyline - + Relative computation of edge tolerance If activated, deflection used for the polygonalisation of each edge will be `ChordalDeflection` &#215; `SizeOfEdge`. The deflection used for the faces will be the maximum deflection of their edges. - + 3D view manipulation shortcuts configuration to mimic other common CAD applications - + Angle increment used to turn(rotate) the 3D view around the normal of the view plane(Z axis frame reference) - + Show or hide by default the trihedron centered at world origin. This doesn't affect 3D view of currently opened documents - + Enable capping of currently clipped graphics - + Enable capping hatch texture of currently clipped graphics @@ -355,86 +362,91 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho + reloadDocumentOnFileChange + Reload Documents On File Changes + + + linkWithDocumentSelector Link With Document Selector - + forceOpenGlFallbackWidget Force OpenGL Fallback Widget - + meshingQuality Quality - + meshingChordalDeflection Chordal Deflection - + meshingAngularDeflection Angular Deflection - + meshingRelative Relative - + navigationStyle View Navigation Style - + defaultShowOriginTrihedron Show Origin Trihedron By Default - + instantZoomFactor Instant Zoom Factor - + turnViewAngleIncrement Turn View Angle Increment - + cappingOn Capping - + cappingHatchOn Capping Hatch - + color Color - + edgeColor Edge Color - + material Material - + showEgesOn Show Edges - + showNodesOn Show Nodes @@ -657,152 +669,152 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho - + All files(*.*) - + Select Part File - - + + Mesh BRep shapes - - + + Import time: {}ms - + New - + New Document - + Anonymous%1 - + Open - + Open Documents - + Recent files - + %1 | %2 - + Clear menu - - + + Import Import - + Import in current document - - + + Export selected items - + No item selected for export - + Select Output File - + Export time: {}ms - + Close "%1" - + Close %1 - + Close - + Close all - + Close all documents - - + + Close all except current - + Close all except current document - + Close all except %1 - + Quit - + Report Bug - + About %1 @@ -873,12 +885,12 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho - + System Information... - + Copy to Clipboard @@ -2405,156 +2417,171 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::Main - + Theme for the UI(classic|dark) - + name - - + + Writes log messages into output file - - + + Don't filter out debug log messages in release build - - + + Show detailed system information and quit - - + + files - + Files to open at startup, optionally - - + + [files...] - + Execute unit tests and exit application - - + + OpenCascade settings file doesn't exist or is not readable [path=%1] - - + + OpenCascade settings file could not be loaded with QSettings [path=%1] - - + + Failed to load translation file [path=%1] - + Settings file(INI format) to load at startup - + Mayo the opensource 3D CAD viewer and converter - - - - - - + + + + + + filepath File Path - + mayo-conv the opensource CAD converter - + Display help on commandline options - + Display version information - + Use settings file(INI format) for the conversion. When this option isn't specified then cached settings are used - + Cache settings file provided with --use-settings for further use - + Write settings cache to an output file(INI format) - + Export opened files into an output file, can be repeated for different formats(eg. -e file.stp -e file.igs...) - + Disable progress reporting in console output - + Files to open(import) - + Error when writing to '%1' - - - Failed to load application settings file [path=%1] + + Settings cache written to %1 + + Settings '%1' cached + + + + + No supplied settings to cache + + + + + Failed to load application settings file [path=%1] + + + + No input files -> nothing to export - + Failed to load theme '%1' @@ -3221,21 +3248,35 @@ Read: %5 - - + + Data - + Graphics - + Options + + + Document file `%1` has been changed since it was opened + +Do you want to reload that document? + +File: `%2` + + + + + Question + + Mayo::WidgetMainHome diff --git a/i18n/mayo_fr.qm b/i18n/mayo_fr.qm index f1c86d5a1cc3099993203fb7befb8ff556631a1f..599f0b2632050d8ac252762980515a8bb9d6f57a 100644 GIT binary patch delta 5237 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 delta 4542 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 Mayo::AppModuleProperties - + language Langue @@ -266,52 +266,61 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Application - + meshing Maillage BRep - + graphics Graphisme - + units Unités - + clipPlanes Plans de coupe - + meshDefaults Maillage par défauts - + import Import - + export Export - + Language used for the application. Change will take effect after application restart Langage de l'application. Tout changement sera effectif après redémarrage de l'application - + + Monitors the file system for changes to documents opened in the application + +When such a file change is detected then the application proposes to reload(open again) the document + Observe le système de fichiers et les changements extérieurs se produisant sur les documents ouverts dans l'application + +Quand un tel changement est détecté alors l'application propose de recharger(ouvrir à nouveau) le document + + + In case where multiple documents are opened, make sure the document displayed in the 3D view corresponds to what is selected in the model tree Dans le cas où plusieurs documents sont ouverts, fait en sort que le document affiché dans la vue 2D correspond à ce qui est sélectionné dans l'arborescence du modèle - + Force usage of the fallback Qt widget to display OpenGL graphics. When `OFF` the application will try to use OpenGL framebuffer for rendering, this allows to display overlay widgets(eg measure tools panel) with translucid background. However using OpenGL framebuffer might cause troubles for some users(eg empty 3D window) especially on macOS. @@ -328,17 +337,17 @@ Quand l'option est activée alors l'application utilisera un widget Qt Cette option est appliquable seulement avec la version ≥ 7.6 d'OpenCascade. Tout changement sera effectif après redémarrage de l'application - + Controls precision of the mesh to be computed from the BRep shape Contrôle la précision du maillage calculé à partir de la forme BRep - + For the tessellation of faces the chordal deflection limits the distance between a curve and its tessellation Pour la tesselation des faces, la déflection chordale limite la distance entre une courbe et sa discrétisation - + For the tessellation of faces the angular deflection limits the angle between subsequent segments in a polyline Pour la tesselation des faces, la déflection angulaire limite l'angle entre les segments successifs d'une polyligne @@ -351,7 +360,7 @@ Cette option est appliquable seulement avec la version ≥ 7.6 d'OpenCascad Pour la tesselation des faces, la déflection angulaire limite l'angle entre les segments successifs d'une polyligne - + Relative computation of edge tolerance If activated, deflection used for the polygonalisation of each edge will be `ChordalDeflection` &#215; `SizeOfEdge`. The deflection used for the faces will be the maximum deflection of their edges. @@ -360,27 +369,27 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Si actif, la déflection utilisée pour la polygonisation de chaque arête sera de `DéflectionChordale` &#215; `TailleArête`. La déflection utilisée pour les faces sera la déflection maximale de ses arêtes. - + 3D view manipulation shortcuts configuration to mimic other common CAD applications Configuration des raccourcis pour manipuler la vue 3D, permet d'imiter les autres application CAO - + Angle increment used to turn(rotate) the 3D view around the normal of the view plane(Z axis frame reference) Incrément angulaire utilisé pour tourner la vue 3D autour de la normale au plan de vue (axe Z de référence) - + Show or hide by default the trihedron centered at world origin. This doesn't affect 3D view of currently opened documents Montrer/cacher par défaut le trièdre positionné à l'orgine "monde". N'affecte pas la vue 3D des documents actuellement ouverts - + Enable capping of currently clipped graphics Activer le bouchage des graphismes actuellement coupés - + Enable capping hatch texture of currently clipped graphics Activer le hachage texturé pour le bouchage des graphismes actuellement coupés @@ -411,86 +420,91 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera + reloadDocumentOnFileChange + Recharder les documents lors d'un changement fichier + + + linkWithDocumentSelector Lier au sélecteur de documents - + forceOpenGlFallbackWidget Forcer usage du widget OpenGL de secours - + meshingQuality Qualité - + meshingChordalDeflection Déflection chordale - + meshingAngularDeflection Déflection angulaire - + meshingRelative Relatif - + navigationStyle Style de navigation de la vue - + defaultShowOriginTrihedron Afficher le trihèdre Origine par défaut - + instantZoomFactor Coefficient du zoom instantané - + turnViewAngleIncrement Incrément de rotation de la vue - + cappingOn Bouchage - + cappingHatchOn Bouchages avec hachures - + color Couleur - + edgeColor Couleur des arêtes - + material Matériau - + showEgesOn Afficher les arêtes - + showNodesOn Afficher les nœuds @@ -540,12 +554,12 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Binary Mayo Document Format - + XML Mayo Document Format - + '%1' doesn't exist or is not readable @@ -717,137 +731,137 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera %1 fichiers (%2) - + All files(*.*) Tous les fichiers (*.*) - + Select Part File Selectionner fichier pièce - - + + Mesh BRep shapes Maillage des formes BRep - - + + Import time: {}ms Durée import: {}ms - + New Nouveau - + New Document Nouveau Document - + Anonymous%1 Anonyme%1 - + Open Ouvrir - + Open Documents Ouvrir des documents - + Recent files Fichiers récents - + %1 | %2 - + Clear menu Vider le menu - - + + Import Importer - + Import in current document Importer dans le document courant - - + + Export selected items Exporter les éléments sélectionnées - + No item selected for export Aucun élément sélectionné pour l'export - + Select Output File Sélection fichier de sortie - + Export time: {}ms Durée export: {}ms - + Close "%1" Fermer "%1" - + Close %1 Fermer %1 - + Close Fermer - + Close all Tout fermer - + Close all documents Fermer tous les documents - - + + Close all except current Tout fermer sauf document courant - + Close all except current document Tout fermer sauf document courant - + Close all except %1 Tout fermer sauf %1 @@ -856,17 +870,17 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Tout fermer sauf "%1" - + Quit Quitter - + Report Bug Signaler un bug - + About %1 À propos de %1 @@ -937,12 +951,12 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Document suivant - + System Information... Informations du système ... - + Copy to Clipboard Copier dans le presse-papiers @@ -2580,24 +2594,24 @@ It can be disabled in order to minimize the size of the resulting file. Mayo, une visionneuse 3D en code libre basée surQt5/OpenCascade - + Theme for the UI(classic|dark) Thème de l'IHM (classic|dark) - + name nom - - + + Writes log messages into output file Écrit les messages de log dans un fichier de sortie - - + + Don't filter out debug log messages in release build Ne pas filtrer les messages de debug dans la version "release" @@ -2606,48 +2620,48 @@ It can be disabled in order to minimize the size of the resulting file. Désactiver l'indicateur de progression dans la sortie console (mode CLI seulement) - - + + Show detailed system information and quit Montrer les informations détaillées du système puis quitter - - + + files files - + Files to open at startup, optionally Fichiers à ouvrir au démarrage, optionnel - - + + [files...] [fichiers ...] - + Execute unit tests and exit application Exécuter les tests unitaires et quitter l'application - - + + OpenCascade settings file doesn't exist or is not readable [path=%1] Le fichier de configuration OpenCascade n'existe pas ou non lisible [chemin=%1] - - + + OpenCascade settings file could not be loaded with QSettings [path=%1] Le fichier de configuration OpenCascade n'a pu être chargé par QSettings [chemin=%1] - - + + Failed to load translation file [path=%1] Échec chargement du fichier de traductions [chemin=%1] @@ -2660,8 +2674,8 @@ It can be disabled in order to minimize the size of the resulting file. Export de {} en cours ... - - + + Failed to load application settings file [path=%1] Échec chargement du fichier de configuration [chemin=%1] @@ -2672,75 +2686,90 @@ It can be disabled in order to minimize the size of the resulting file. - + Settings file(INI format) to load at startup Fichier de configuration (format INI) à charger au démarrage - + Mayo the opensource 3D CAD viewer and converter Mayo le visualiseur et convertisseur 3D pour la CAO - - - - - - + + + + + + filepath - + mayo-conv the opensource CAD converter mayo-conv le convertisseur CAO opensource - + Display help on commandline options Affichage de l'aide des options en ligne de commande - + Display version information Affichage de l'information de version - + Use settings file(INI format) for the conversion. When this option isn't specified then cached settings are used Utiliser le fichier de paramètres (format INI) pour la conversion. Quand cette option n'est pas spécifiée alors les paramètres sauvegardés sont utilisés - + Cache settings file provided with --use-settings for further use Sauvegarder le fichier de paramètres fourni via --use-settings pour une utilisation ultérieure - + Write settings cache to an output file(INI format) Écrire les paramètres sauvegardés dans un fichier de sortie (format INI) - + Export opened files into an output file, can be repeated for different formats(eg. -e file.stp -e file.igs...) Exporter des fichiers dans un fichier de sortie, répétable selon les différents formats supportés (par exemple -e file.stp -e file.igs ...) - + Disable progress reporting in console output Désactiver indication du progrès dans la sortie console - + Files to open(import) Fichiers à ouvrir (import) - + Error when writing to '%1' Erreur lors de l'écriture vers '%1' + + + Settings cache written to %1 + + + + + Settings '%1' cached + + + + + No supplied settings to cache + + Mesh BRep shapes Maillage des formes BRep @@ -2762,12 +2791,12 @@ It can be disabled in order to minimize the size of the resulting file. Export de %1 en cours ... - + No input files -> nothing to export Auncun fichier en entrée -> aucun export - + Failed to load theme '%1' Impossible de charger le thème '%1' @@ -3694,21 +3723,35 @@ Lu: %5 Z= - - + + Data Données - + Graphics Graphismes - + Options Options + + + Document file `%1` has been changed since it was opened + +Do you want to reload that document? + +File: `%2` + + + + + Question + + Mayo::WidgetMainHome diff --git a/src/app/app_module_properties.cpp b/src/app/app_module_properties.cpp index 43274bf2..ade11d58 100644 --- a/src/app/app_module_properties.cpp +++ b/src/app/app_module_properties.cpp @@ -44,6 +44,7 @@ AppModuleProperties::AppModuleProperties(Settings* settings) settings->addSetting(&this->recentFiles, groupId_application); settings->addSetting(&this->lastOpenDir, groupId_application); settings->addSetting(&this->lastSelectedFormatFilter, groupId_application); + settings->addSetting(&this->reloadDocumentOnFileChange, groupId_application); settings->addSetting(&this->linkWithDocumentSelector, groupId_application); settings->addSetting(&this->forceOpenGlFallbackWidget, groupId_application); this->recentFiles.setUserVisible(false); @@ -82,6 +83,7 @@ AppModuleProperties::AppModuleProperties(Settings* settings) this->recentFiles.setValue({}); this->lastOpenDir.setValue({}); this->lastSelectedFormatFilter.setValue({}); + this->reloadDocumentOnFileChange.setValue(true); this->linkWithDocumentSelector.setValue(true); #ifndef MAYO_OS_MAC this->forceOpenGlFallbackWidget.setValue(false); @@ -159,10 +161,16 @@ void AppModuleProperties::retranslate() // Application this->language.setDescription( - textIdTr("Language used for the application. Change will take effect after application restart")); + textIdTr("Language used for the application. Change will take effect after application restart") + ); + this->reloadDocumentOnFileChange.setDescription( + textIdTr("Monitors the file system for changes to documents opened in the application\n\n" + "When such a file change is detected then the application proposes to reload(open again) the document") + ); this->linkWithDocumentSelector.setDescription( textIdTr("In case where multiple documents are opened, make sure the document displayed in " - "the 3D view corresponds to what is selected in the model tree")); + "the 3D view corresponds to what is selected in the model tree") + ); this->forceOpenGlFallbackWidget.setDescription( textIdTr("Force usage of the fallback Qt widget to display OpenGL graphics.\n\n" "When `OFF` the application will try to use OpenGL framebuffer for rendering, " @@ -178,33 +186,42 @@ void AppModuleProperties::retranslate() // Meshing this->meshingQuality.setDescription( - textIdTr("Controls precision of the mesh to be computed from the BRep shape")); + textIdTr("Controls precision of the mesh to be computed from the BRep shape") + ); this->meshingChordalDeflection.setDescription( textIdTr("For the tessellation of faces the chordal deflection limits the distance between " - "a curve and its tessellation")); + "a curve and its tessellation") + ); this->meshingAngularDeflection.setDescription( textIdTr("For the tessellation of faces the angular deflection limits the angle between " - "subsequent segments in a polyline")); + "subsequent segments in a polyline") + ); this->meshingRelative.setDescription( textIdTr("Relative computation of edge tolerance\n\n" "If activated, deflection used for the polygonalisation of each edge will be " "`ChordalDeflection` × `SizeOfEdge`. The deflection used for the faces will be " - "the maximum deflection of their edges.")); + "the maximum deflection of their edges.") + ); // Graphics this->navigationStyle.setDescription( - textIdTr("3D view manipulation shortcuts configuration to mimic other common CAD applications")); + textIdTr("3D view manipulation shortcuts configuration to mimic other common CAD applications") + ); this->turnViewAngleIncrement.setDescription( - textIdTr("Angle increment used to turn(rotate) the 3D view around the normal of the view plane(Z axis frame reference)")); + textIdTr("Angle increment used to turn(rotate) the 3D view around the normal of the view plane(Z axis frame reference)") + ); // -- Graphics/ClipPlanes this->defaultShowOriginTrihedron.setDescription( textIdTr("Show or hide by default the trihedron centered at world origin. " - "This doesn't affect 3D view of currently opened documents")); + "This doesn't affect 3D view of currently opened documents") + ); this->clipPlanesCappingOn.setDescription( - textIdTr("Enable capping of currently clipped graphics")); + textIdTr("Enable capping of currently clipped graphics") + ); this->clipPlanesCappingHatchOn.setDescription( - textIdTr("Enable capping hatch texture of currently clipped graphics")); + textIdTr("Enable capping hatch texture of currently clipped graphics") + ); } void AppModuleProperties::onPropertyChanged(Property* prop) diff --git a/src/app/app_module_properties.h b/src/app/app_module_properties.h index 2961e69e..32fcf03e 100644 --- a/src/app/app_module_properties.h +++ b/src/app/app_module_properties.h @@ -51,6 +51,7 @@ class AppModuleProperties : public PropertyGroup { PropertyRecentFiles recentFiles{ this, textId("recentFiles") }; PropertyFilePath lastOpenDir{ this, textId("lastOpenFolder") }; PropertyString lastSelectedFormatFilter{ this, textId("lastSelectedFormatFilter") }; + PropertyBool reloadDocumentOnFileChange{ this, textId("reloadDocumentOnFileChange") }; PropertyBool linkWithDocumentSelector{ this, textId("linkWithDocumentSelector") }; PropertyBool forceOpenGlFallbackWidget{ this, textId("forceOpenGlFallbackWidget") }; // Meshing diff --git a/src/app/commands_file.cpp b/src/app/commands_file.cpp index a4ce96ea..8c9d00b8 100644 --- a/src/app/commands_file.cpp +++ b/src/app/commands_file.cpp @@ -100,7 +100,8 @@ struct OpenFileNames { static OpenFileNames get( QWidget* parentWidget, - OpenFileNames::GetOption option = OpenFileNames::GetMany) + OpenFileNames::GetOption option = OpenFileNames::GetMany + ) { OpenFileNames result; result.selectedFormat = IO::Format_Unknown; @@ -118,14 +119,16 @@ struct OpenFileNames { if (option == OpenFileNames::GetOne) { const QString strFilepath = QFileDialog::getOpenFileName( - parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter); + parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter + ); result.listFilepath.clear(); result.listFilepath.push_back(filepathFrom(strFilepath)); } else { const QStringList listStrFilePath = QFileDialog::getOpenFileNames( - parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter); + parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter + ); result.listFilepath.clear(); for (const QString& strFilePath : listStrFilePath) result.listFilepath.push_back(filepathFrom(strFilePath)); @@ -211,9 +214,52 @@ void FileCommandTools::openDocumentsFromList(IAppContext* context, Span(&fp, 1)); + FileCommandTools::openDocumentsFromList(context, Span(&filePath, 1)); +} + +void FileCommandTools::importInDocument( + IAppContext* context, const DocumentPtr& targetDoc, Span listFilePaths + ) +{ + auto appModule = AppModule::get(); + const Document::Identifier targetDocId = targetDoc->identifier(); + const TaskId taskId = context->taskMgr()->newTask([=](TaskProgress* progress) { + QElapsedTimer chrono; + chrono.start(); + + auto doc = Application::instance()->findDocumentByIdentifier(targetDocId); + const bool okImport = appModule->ioSystem()->importInDocument() + .targetDocument(doc) + .withFilepaths(listFilePaths) + .withParametersProvider(appModule) + .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { + appModule->computeBRepMesh(labelEntity, progress); + }) + .withEntityPostProcessRequiredIf(&IO::formatProvidesBRep) + .withEntityPostProcessInfoProgress(20, Command::textIdTr("Mesh BRep shapes")) + .withMessenger(appModule) + .withTaskProgress(progress) + .execute(); + if (okImport) + appModule->emitInfo(fmt::format(Command::textIdTr("Import time: {}ms"), chrono.elapsed())); + }); + const QString taskTitle = + listFilePaths.size() > 1 ? + Command::tr("Import") : + filepathTo(listFilePaths.front().stem()); + context->taskMgr()->setTitle(taskId, to_stdString(taskTitle)); + context->taskMgr()->run(taskId); +} + +void FileCommandTools::importInDocument( + IAppContext* context, + const DocumentPtr& targetDoc, + const FilePath& filePath + ) +{ + FileCommandTools::importInDocument(context, targetDoc, Span(&filePath, 1)); } CommandNewDocument::CommandNewDocument(IAppContext* context) @@ -354,40 +400,15 @@ void CommandImportInCurrentDocument::execute() if (resFileNames.listFilepath.empty()) return; - auto appModule = AppModule::get(); - const TaskId taskId = this->taskMgr()->newTask([=](TaskProgress* progress) { - QElapsedTimer chrono; - chrono.start(); - - const bool okImport = appModule->ioSystem()->importInDocument() - .targetDocument(guiDoc->document()) - .withFilepaths(resFileNames.listFilepath) - .withParametersProvider(appModule) - .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { - appModule->computeBRepMesh(labelEntity, progress); - }) - .withEntityPostProcessRequiredIf(&IO::formatProvidesBRep) - .withEntityPostProcessInfoProgress(20, Command::textIdTr("Mesh BRep shapes")) - .withMessenger(appModule) - .withTaskProgress(progress) - .execute(); - if (okImport) - appModule->emitInfo(fmt::format(Command::textIdTr("Import time: {}ms"), chrono.elapsed())); - }); - const QString taskTitle = - resFileNames.listFilepath.size() > 1 ? - Command::tr("Import") : - filepathTo(resFileNames.listFilepath.front().stem()); - this->taskMgr()->setTitle(taskId, to_stdString(taskTitle)); - this->taskMgr()->run(taskId); + FileCommandTools::importInDocument(this->context(), guiDoc->document(), resFileNames.listFilepath); for (const FilePath& fp : resFileNames.listFilepath) - appModule->prependRecentFile(fp); + AppModule::get()->prependRecentFile(fp); } bool CommandImportInCurrentDocument::getEnabledStatus() const { return this->app()->documentCount() != 0 - && this->context()->currentPage() == IAppContext::Page::Documents; + && this->context()->currentPage() == IAppContext::Page::Documents; } CommandExportSelectedApplicationItems::CommandExportSelectedApplicationItems(IAppContext* context) diff --git a/src/app/commands_file.h b/src/app/commands_file.h index 65db0ec7..f031f107 100644 --- a/src/app/commands_file.h +++ b/src/app/commands_file.h @@ -16,7 +16,17 @@ class FileCommandTools { public: static void closeDocument(IAppContext* context, Document::Identifier docId); static void openDocumentsFromList(IAppContext* context, Span listFilePath); - static void openDocument(IAppContext* context, const FilePath& fp); + static void openDocument(IAppContext* context, const FilePath& filePath); + static void importInDocument( + IAppContext* context, + const DocumentPtr& targetDoc, + Span listFilePaths + ); + static void importInDocument( + IAppContext* context, + const DocumentPtr& targetDoc, + const FilePath& filePath + ); }; class CommandNewDocument : public Command { diff --git a/src/app/document_files_watcher.cpp b/src/app/document_files_watcher.cpp new file mode 100644 index 00000000..f57477e6 --- /dev/null +++ b/src/app/document_files_watcher.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "document_files_watcher.h" + +#include "../base/application.h" +#include "../qtcommon/filepath_conv.h" + +#include + +namespace Mayo { + +DocumentFilesWatcher::DocumentFilesWatcher(const ApplicationPtr& app, QObject* parent) + : QObject(parent), + m_app(app) +{ + app->signalDocumentAdded.connectSlot(&DocumentFilesWatcher::onDocumentAdded, this); + app->signalDocumentAboutToClose.connectSlot(&DocumentFilesWatcher::onDocumentAboutToClose, this); + app->signalDocumentFilePathChanged.connectSlot(&DocumentFilesWatcher::onDocumentFilePathChanged, this); +} + +void DocumentFilesWatcher::enable(bool on) +{ + if (m_isEnabled == on) + return; + + m_isEnabled = on; + if (on) { + for (Application::DocumentIterator itDoc(m_app); itDoc.hasNext(); itDoc.next()) { + const FilePath& docFilePath = itDoc.current()->filePath(); + const QString strDocFilePath = filepathTo(docFilePath); + if (filepathExists(docFilePath)) + this->fileSystemWatcher()->addPath(strDocFilePath); + } + } + else { + this->destroyFileSystemWatcher(); + m_vecNonAckDocumentChanged.clear(); + } +} + +bool DocumentFilesWatcher::isEnabled() const +{ + return m_isEnabled; +} + +void DocumentFilesWatcher::acknowledgeDocumentFileChange(const DocumentPtr& doc) +{ + auto it = std::find(m_vecNonAckDocumentChanged.begin(), m_vecNonAckDocumentChanged.end(), doc); + if (it != m_vecNonAckDocumentChanged.end()) + m_vecNonAckDocumentChanged.erase(it); +} + +QFileSystemWatcher* DocumentFilesWatcher::fileSystemWatcher() +{ + if (!m_fileSystemWatcher) { + m_fileSystemWatcher = new QFileSystemWatcher(this); + QObject::connect( + m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, + this, &DocumentFilesWatcher::onFileChanged + ); + } + + return m_fileSystemWatcher; +} + +void DocumentFilesWatcher::destroyFileSystemWatcher() +{ + delete m_fileSystemWatcher; + m_fileSystemWatcher = nullptr; +} + +void DocumentFilesWatcher::onFileChanged(const QString& strFilePath) +{ + if (m_isEnabled) { + const FilePath docFilePath = filepathFrom(strFilePath); + DocumentPtr doc = m_app->findDocumentByLocation(docFilePath); + if (this->isDocumentChangeAcknowledged(doc)) { + m_vecNonAckDocumentChanged.push_back(doc); + this->signalDocumentFileChanged.send(doc); + } + } +} + +bool DocumentFilesWatcher::isDocumentChangeAcknowledged(const DocumentPtr& doc) const +{ + auto it = std::find(m_vecNonAckDocumentChanged.cbegin(), m_vecNonAckDocumentChanged.cend(), doc); + return it == m_vecNonAckDocumentChanged.cend(); +} + +void DocumentFilesWatcher::onDocumentFilePathChanged(const DocumentPtr&, const FilePath& fp) +{ + if (m_isEnabled) { + if (filepathExists(fp)) + this->fileSystemWatcher()->addPath(filepathTo(fp)); + } +} + +void DocumentFilesWatcher::onDocumentAdded(const DocumentPtr& doc) +{ + if (m_isEnabled) { + if (filepathExists(doc->filePath())) + this->fileSystemWatcher()->addPath(filepathTo(doc->filePath())); + } +} + +void DocumentFilesWatcher::onDocumentAboutToClose(const DocumentPtr& doc) +{ + if (m_isEnabled) + this->fileSystemWatcher()->removePath(filepathTo(doc->filePath())); +} + + +} // namespace Mayo diff --git a/src/app/document_files_watcher.h b/src/app/document_files_watcher.h new file mode 100644 index 00000000..ea6bd4df --- /dev/null +++ b/src/app/document_files_watcher.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** Copyright (c) 2024, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "../base/application_ptr.h" +#include "../base/document_ptr.h" +#include "../base/filepath.h" +#include "../base/signal.h" + +#include + +#include + +class QFileSystemWatcher; + +namespace Mayo { + +// Monitors the file system for changes to Document files owned by Application object +// When a Document is opened then DocumentFilesWatcher automatically monitors the corresponding file +// for changes. When an Document is closed then monitoring for that file is terminated +// File changes need to be acknowledged with DocumentFilesWatcher:acknowledgeDocumentFileChange() +// otherwise there won't be further signal notification for the changed file +class DocumentFilesWatcher : public QObject { +public: + DocumentFilesWatcher(const ApplicationPtr& app, QObject* parent = nullptr); + + Signal signalDocumentFileChanged; + + void enable(bool on); + bool isEnabled() const; + + void acknowledgeDocumentFileChange(const DocumentPtr& doc); + +private: + QFileSystemWatcher* fileSystemWatcher(); + void destroyFileSystemWatcher(); + void onFileChanged(const QString& strFilePath); + + bool isDocumentChangeAcknowledged(const DocumentPtr& doc) const; + + void onDocumentFilePathChanged(const DocumentPtr& doc, const FilePath& fp); + void onDocumentAdded(const DocumentPtr& doc); + void onDocumentAboutToClose(const DocumentPtr& doc); + + ApplicationPtr m_app; + QFileSystemWatcher* m_fileSystemWatcher = nullptr; + bool m_isEnabled = false; + std::vector m_vecNonAckDocumentChanged; +}; + +} // namespace Mayo diff --git a/src/app/widget_main_control.cpp b/src/app/widget_main_control.cpp index 42704da2..6b764793 100644 --- a/src/app/widget_main_control.cpp +++ b/src/app/widget_main_control.cpp @@ -17,9 +17,11 @@ #include "commands_api.h" #include "commands_file.h" #include "commands_window.h" +#include "document_files_watcher.h" #include "document_property_group.h" #include "gui_document_list_model.h" #include "item_view_buttons.h" +#include "qtwidgets_utils.h" #include "theme.h" #include "widget_file_system.h" #include "widget_gui_document.h" @@ -27,8 +29,10 @@ #include "widget_occ_view.h" #include "widget_properties_editor.h" +#include #include #include +#include #include namespace Mayo { @@ -36,7 +40,8 @@ namespace Mayo { WidgetMainControl::WidgetMainControl(GuiApplication* guiApp, QWidget* parent) : IWidgetMainPage(parent), m_ui(new Ui_WidgetMainControl), - m_guiApp(guiApp) + m_guiApp(guiApp), + m_docFilesWatcher(new DocumentFilesWatcher(guiApp->application(), this)) { assert(m_guiApp != nullptr); @@ -85,6 +90,18 @@ WidgetMainControl::WidgetMainControl(GuiApplication* guiApp, QWidget* parent) guiApp->selectionModel()->signalChanged.connectSlot(&WidgetMainControl::onApplicationItemSelectionChanged, this); guiApp->signalGuiDocumentAdded.connectSlot(&WidgetMainControl::onGuiDocumentAdded, this); + // Document files monitoring + auto appModule = AppModule::get(); + const auto& propReloadDocOnFileChange = appModule->properties()->reloadDocumentOnFileChange; + m_docFilesWatcher->enable(propReloadDocOnFileChange); + appModule->settings()->signalChanged.connectSlot([&](const Property* property) { + if (property == &propReloadDocOnFileChange) { + m_docFilesWatcher->enable(propReloadDocOnFileChange); + m_pendingDocsToReload.clear(); + } + }); + m_docFilesWatcher->signalDocumentFileChanged.connectSlot(&WidgetMainControl::onDocumentFileChanged, this); + // Creation of annex objects m_listViewBtns = new ItemViewButtons(m_ui->listView_OpenedDocuments, this); m_listViewBtns->installDefaultItemDelegate(); @@ -310,6 +327,30 @@ QWidget* WidgetMainControl::recreateLeftHeaderPlaceHolder() return placeHolder; } +void WidgetMainControl::reloadDocumentAfterChange(const DocumentPtr& doc) +{ + const QString strQuestion = + tr("Document file `%1` has been changed since it was opened\n\n" + "Do you want to reload that document?\n\n" + "File: `%2`") + .arg(to_QString(doc->name())) + .arg(QDir::toNativeSeparators(filepathTo(doc->filePath()))) + ; + const auto msgBtns = QMessageBox::Yes | QMessageBox::No; + auto msgBox = new QMessageBox(QMessageBox::Question, tr("Question"), strQuestion, msgBtns, this); + msgBox->setTextFormat(Qt::MarkdownText); + QtWidgetsUtils::asyncDialogExec(msgBox); + QObject::connect(msgBox, &QMessageBox::buttonClicked, this, [=](QAbstractButton* btn) { + m_docFilesWatcher->acknowledgeDocumentFileChange(doc); + if (btn == msgBox->button(QMessageBox::Yes)) { + while (doc->entityCount() > 0) + doc->destroyEntity(doc->entityTreeNodeId(0)); + + FileCommandTools::importInDocument(m_appContext, doc, doc->filePath()); + } + }); +} + WidgetGuiDocument* WidgetMainControl::widgetGuiDocument(int idx) const { return qobject_cast(m_ui->stack_GuiDocuments->widget(idx)); @@ -412,7 +453,33 @@ void WidgetMainControl::onCurrentDocumentIndexChanged(int idx) const FilePath docFilePath = docPtr ? docPtr->filePath() : FilePath(); m_ui->widget_FileSystem->setLocation(filepathTo(docFilePath)); + if (m_docFilesWatcher->isEnabled()) { + auto itDoc = m_pendingDocsToReload.find(docPtr); + if (itDoc != m_pendingDocsToReload.end()) { + m_pendingDocsToReload.erase(itDoc); + this->reloadDocumentAfterChange(docPtr); + } + } + emit this->currentDocumentIndexChanged(idx); } +void WidgetMainControl::onDocumentFileChanged(const DocumentPtr& doc) +{ + WidgetGuiDocument* widgetDoc = nullptr; + for (int i = 0; i < this->widgetGuiDocumentCount() && !widgetDoc; ++i) { + const DocumentPtr& candidateDoc = this->widgetGuiDocument(i)->guiDocument()->document(); + if (candidateDoc->identifier() == doc->identifier()) + widgetDoc = this->widgetGuiDocument(i); + } + + if (!widgetDoc) + return; + + if (widgetDoc == this->currentWidgetGuiDocument()) + this->reloadDocumentAfterChange(doc); + else + m_pendingDocsToReload.insert(doc); +} + } // namespace Mayo diff --git a/src/app/widget_main_control.h b/src/app/widget_main_control.h index 9c9d5270..65298466 100644 --- a/src/app/widget_main_control.h +++ b/src/app/widget_main_control.h @@ -6,10 +6,14 @@ #pragma once +#include "../base/document_ptr.h" +#include "../base/filepath.h" #include "../base/property.h" +#include "../base/tkernel_utils.h" #include "iwidget_main_page.h" #include +#include class QFileInfo; class QMenu; @@ -18,6 +22,7 @@ namespace Mayo { class IAppContext; class CommandContainer; +class DocumentFilesWatcher; class GuiApplication; class GuiDocument; class ItemViewButtons; @@ -57,13 +62,14 @@ class WidgetMainControl : public IWidgetMainPage { void onApplicationItemSelectionChanged(); void onLeftContentsPageChanged(int pageId); void onWidgetFileSystemLocationActivated(const QFileInfo& loc); + void onGuiDocumentAdded(GuiDocument* guiDoc); + void onCurrentDocumentIndexChanged(int idx); + void onDocumentFileChanged(const DocumentPtr& doc); QWidget* findLeftHeaderPlaceHolder() const; QWidget* recreateLeftHeaderPlaceHolder(); - void onGuiDocumentAdded(GuiDocument* guiDoc); - - void onCurrentDocumentIndexChanged(int idx); + void reloadDocumentAfterChange(const DocumentPtr& doc); class Ui_WidgetMainControl* m_ui = nullptr; GuiApplication* m_guiApp = nullptr; @@ -71,6 +77,8 @@ class WidgetMainControl : public IWidgetMainPage { ItemViewButtons* m_listViewBtns = nullptr; std::unique_ptr m_ptrCurrentNodeDataProperties; std::unique_ptr m_ptrCurrentNodeGraphicsProperties; + DocumentFilesWatcher* m_docFilesWatcher = nullptr; + std::unordered_set m_pendingDocsToReload; }; } // namespace Mayo From 33b09e0c35ddeb1b12f3f6ac12422f4b70b673ff Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Wed, 3 Apr 2024 17:44:07 +0200 Subject: [PATCH 045/101] Tests: add unit tests for app/DocumentFilesWatcher --- tests/test_app.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test_app.h | 2 ++ 2 files changed, 60 insertions(+) diff --git a/tests/test_app.cpp b/tests/test_app.cpp index a0e22498..8adc7353 100644 --- a/tests/test_app.cpp +++ b/tests/test_app.cpp @@ -12,11 +12,16 @@ #include "test_app.h" #include "../src/app/app_module.h" +#include "../src/app/document_files_watcher.h" #include "../src/app/qstring_utils.h" #include "../src/app/qtgui_utils.h" #include "../src/app/recent_files.h" #include "../src/app/theme.h" +#include "../src/base/application.h" +#include "../src/base/document.h" +#include "../src/base/io_system.h" #include "../src/io_occ/io_occ.h" +#include "../src/io_ply/io_ply_reader.h" #include "../src/qtcommon/filepath_conv.h" #include "../src/qtcommon/qstring_conv.h" @@ -62,6 +67,59 @@ RecentFile createRecentFile(const QPixmap& thumbnail) } // namespace +void TestApp::DocumentFilesWatcher_test() +{ + IO::System ioSystem; + ioSystem.addFactoryReader(std::make_unique()); + + auto app = Application::instance(); + + DocumentFilesWatcher docFilesWatcher(app); + docFilesWatcher.enable(true); + Document::Identifier changedDocId = -1; + docFilesWatcher.signalDocumentFileChanged.connectSlot([&](DocumentPtr changedDoc) { + changedDocId = changedDoc->identifier(); + }); + + const FilePath cadFilePath = "tests/outputs/temp-cube.ply"; + auto fnCopyCadFile = [=]{ + std_filesystem::copy_file( + "tests/inputs/cube.ply", + cadFilePath, + std_filesystem::copy_options::overwrite_existing + ); + }; + + DocumentPtr doc = app->newDocument(); + doc->setFilePath(cadFilePath); + fnCopyCadFile(); + auto _ = gsl::finally([=]{ app->closeDocument(doc); }); + const bool okImport = ioSystem.importInDocument() + .targetDocument(doc) + .withFilepath(cadFilePath) + .execute(); + QVERIFY(okImport); + + // Check file change on document is caught + fnCopyCadFile(); + const bool okWait = QTest::qWaitFor([&]{ return changedDocId != -1; }); + QVERIFY(okWait); + QCOMPARE(changedDocId, doc->identifier()); + + // Check further file changes are not monitored until last one is acknowledged + changedDocId = -1; + fnCopyCadFile(); + QTest::qWait(125/*ms*/); + QCOMPARE(changedDocId, -1); + + // Check closing document unmonitors file in DocumentFilesWatcher + docFilesWatcher.acknowledgeDocumentFileChange(doc); + app->closeDocument(doc); + fnCopyCadFile(); + QTest::qWait(125/*ms*/); + QCOMPARE(changedDocId, -1); +} + void TestApp::FilePathConv_test() { const char strTestPath[] = "../as1-oc-214 - 測試文件.stp"; diff --git a/tests/test_app.h b/tests/test_app.h index 6ba45d09..28dd1867 100644 --- a/tests/test_app.h +++ b/tests/test_app.h @@ -14,6 +14,8 @@ namespace Mayo { class TestApp : public QObject { Q_OBJECT private slots: + void DocumentFilesWatcher_test(); + void FilePathConv_test(); void QStringUtils_append_test(); From c901aa20393e33c8c3c30317369370b0e057ce29 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 4 Apr 2024 08:21:11 +0200 Subject: [PATCH 046/101] Tests: fix TestApp::DocumentFilesWatcher_test() for Linux --- tests/test_app.cpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/test_app.cpp b/tests/test_app.cpp index 8adc7353..0fae5e22 100644 --- a/tests/test_app.cpp +++ b/tests/test_app.cpp @@ -19,9 +19,6 @@ #include "../src/app/theme.h" #include "../src/base/application.h" #include "../src/base/document.h" -#include "../src/base/io_system.h" -#include "../src/io_occ/io_occ.h" -#include "../src/io_ply/io_ply_reader.h" #include "../src/qtcommon/filepath_conv.h" #include "../src/qtcommon/qstring_conv.h" @@ -69,9 +66,6 @@ RecentFile createRecentFile(const QPixmap& thumbnail) void TestApp::DocumentFilesWatcher_test() { - IO::System ioSystem; - ioSystem.addFactoryReader(std::make_unique()); - auto app = Application::instance(); DocumentFilesWatcher docFilesWatcher(app); @@ -90,15 +84,13 @@ void TestApp::DocumentFilesWatcher_test() ); }; + fnCopyCadFile(); DocumentPtr doc = app->newDocument(); doc->setFilePath(cadFilePath); - fnCopyCadFile(); - auto _ = gsl::finally([=]{ app->closeDocument(doc); }); - const bool okImport = ioSystem.importInDocument() - .targetDocument(doc) - .withFilepath(cadFilePath) - .execute(); - QVERIFY(okImport); + auto _ = gsl::finally([=]{ + if (app->findIndexOfDocument(doc) != -1) + app->closeDocument(doc); + }); // Check file change on document is caught fnCopyCadFile(); From 4a393bbf36cc4cad38ea4e3bdf7e2078906cbc52 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 4 Apr 2024 09:51:17 +0200 Subject: [PATCH 047/101] App: fix crash in Options dialog with meshQuality setting Relates to GitHub #264 --- src/app/dialog_options.cpp | 11 ++++++----- src/app/dialog_options.h | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/dialog_options.cpp b/src/app/dialog_options.cpp index ad31d7d2..1c27c758 100644 --- a/src/app/dialog_options.cpp +++ b/src/app/dialog_options.cpp @@ -205,22 +205,22 @@ DialogOptions::DialogOptions(Settings* settings, QWidget* parent) } // Enable/disable editor widget when the corresponding setting status is changed - settings->signalEnabled.connectSlot([=](const Property* setting, bool on) { + m_connSettingsEnabled = settings->signalEnabled.connectSlot([=](const Property* setting, bool on) { QWidget* editor = CppUtils::findValue(setting, m_mapSettingEditor); if (editor) editor->setEnabled(on); }); // Backup initial value of changed settings, so they can be restored on dialog cancellation - m_connSettingsAboutToChange = m_settings->signalAboutToChange.connectSlot([=](Property* property) { + m_connSettingsAboutToChange = settings->signalAboutToChange.connectSlot([=](Property* property) { if (m_mapSettingInitialValue.find(property) == m_mapSettingInitialValue.cend()) { - const Settings::Variant propertyValue = m_settings->propertyValueConversion().toVariant(*property); + const Settings::Variant propertyValue = settings->propertyValueConversion().toVariant(*property); m_mapSettingInitialValue.insert({ property, propertyValue}); } }); // Synchronize editor widget when value of the corresponding property is changed - m_connSettingsChanged = m_settings->signalChanged.connectSlot([=](const Property* property) { + m_connSettingsChanged = settings->signalChanged.connectSlot([=](const Property* property) { auto itFound = m_mapSettingEditor.find(property); if (itFound != m_mapSettingEditor.cend()) this->syncEditor(itFound->second); @@ -262,7 +262,7 @@ DialogOptions::DialogOptions(Settings* settings, QWidget* parent) // Action for "Restore defaults" button auto btnRestoreDefaults = m_ui->buttonBox->button(QDialogButtonBox::RestoreDefaults); - QObject::connect(btnRestoreDefaults, &QPushButton::clicked, this, [=]{ m_settings->resetAll(); }); + QObject::connect(btnRestoreDefaults, &QPushButton::clicked, this, [=]{ settings->resetAll(); }); // Actions for "Exchange" button auto btnExchange = m_ui->buttonBox->addButton(tr("Exchange"), QDialogButtonBox::ActionRole); @@ -278,6 +278,7 @@ DialogOptions::~DialogOptions() { m_connSettingsAboutToChange.disconnect(); m_connSettingsChanged.disconnect(); + m_connSettingsEnabled.disconnect(); delete m_ui; } diff --git a/src/app/dialog_options.h b/src/app/dialog_options.h index 1025ac54..ac10b6e3 100644 --- a/src/app/dialog_options.h +++ b/src/app/dialog_options.h @@ -42,6 +42,7 @@ class DialogOptions : public QDialog { Settings* m_settings = nullptr; SignalConnectionHandle m_connSettingsAboutToChange; SignalConnectionHandle m_connSettingsChanged; + SignalConnectionHandle m_connSettingsEnabled; }; } // namespace Mayo From 09dbd50206d9f4796a249e953ea6509fdde102b3 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 11 Apr 2024 10:21:07 +0200 Subject: [PATCH 048/101] Base: fix format probing for VRML files Relates to GitHub #268 --- src/base/io_system.cpp | 25 +++--- tests/inputs/cube.wrl | 167 +++++++++++++++++++++++++++++++++++++++++ tests/test_base.cpp | 1 + 3 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 tests/inputs/cube.wrl diff --git a/src/base/io_system.cpp b/src/base/io_system.cpp index 166e827f..63e2c752 100644 --- a/src/base/io_system.cpp +++ b/src/base/io_system.cpp @@ -536,7 +536,14 @@ System::Operation_ImportInDocument::Operation_ImportInDocument(System& system) namespace { -bool matchRegExp(std::string_view str, const std::regex& rx) +bool matchRegExp_atStart(std::string_view str, const std::regex& rx) +{ + std::match_results mres; + const bool match = std::regex_search(str.cbegin(), str.cend(), mres, rx); + return match ? mres.position() == 0 : false; +} + +bool matchRegExp_anyWhere(std::string_view str, const std::regex& rx) { return std::regex_search(str.cbegin(), str.cend(), rx); } @@ -546,19 +553,19 @@ bool matchRegExp(std::string_view str, const std::regex& rx) Format probeFormat_STEP(const System::FormatProbeInput& input) { const std::regex rx{ R"(^\s*ISO-10303-21\s*;\s*HEADER)" }; - return matchRegExp(input.contentsBegin, rx) ? Format_STEP : Format_Unknown; + return matchRegExp_atStart(input.contentsBegin, rx) ? Format_STEP : Format_Unknown; } Format probeFormat_IGES(const System::FormatProbeInput& input) { const std::regex rx{ R"(^.{72}S\s*[0-9]+\s*[\n\r\f])" }; - return matchRegExp(input.contentsBegin, rx) ? Format_IGES : Format_Unknown; + return matchRegExp_atStart(input.contentsBegin, rx) ? Format_IGES : Format_Unknown; } Format probeFormat_OCCBREP(const System::FormatProbeInput& input) { const std::regex rx{ R"(^\s*DBRep_DrawableShape)" }; - return matchRegExp(input.contentsBegin, rx) ? Format_OCCBREP : Format_Unknown; + return matchRegExp_atStart(input.contentsBegin, rx) ? Format_OCCBREP : Format_Unknown; } Format probeFormat_STL(const System::FormatProbeInput& input) @@ -584,8 +591,8 @@ Format probeFormat_STL(const System::FormatProbeInput& input) // ASCII STL ? { - const std::regex rx{ R"(^\s*solid)" }; - if (matchRegExp(input.contentsBegin, rx)) + const std::regex rx{ R"(^\s*solid\s+)" }; + if (matchRegExp_atStart(input.contentsBegin, rx)) return Format_STL; } @@ -595,19 +602,19 @@ Format probeFormat_STL(const System::FormatProbeInput& input) Format probeFormat_OBJ(const System::FormatProbeInput& input) { const std::regex rx{ R"([^\n]\s*(v|vt|vn|vp|surf)\s+[-\+]?[0-9\.]+\s)" }; - return matchRegExp(input.contentsBegin, rx) ? Format_OBJ : Format_Unknown; + return matchRegExp_anyWhere(input.contentsBegin, rx) ? Format_OBJ : Format_Unknown; } Format probeFormat_PLY(const System::FormatProbeInput& input) { const std::regex rx{ R"(^\s*ply\s+format\s+(ascii|binary_little_endian|binary_big_endian)\s+)" }; - return matchRegExp(input.contentsBegin, rx) ? Format_PLY : Format_Unknown; + return matchRegExp_atStart(input.contentsBegin, rx) ? Format_PLY : Format_Unknown; } Format probeFormat_OFF(const System::FormatProbeInput& input) { const std::regex rx{ R"(^\s*[CN4]?OFF\s+)" }; - return matchRegExp(input.contentsBegin, rx) ? Format_OFF : Format_Unknown; + return matchRegExp_atStart(input.contentsBegin, rx) ? Format_OFF : Format_Unknown; } void addPredefinedFormatProbes(System* system) diff --git a/tests/inputs/cube.wrl b/tests/inputs/cube.wrl new file mode 100644 index 00000000..f7a2ca20 --- /dev/null +++ b/tests/inputs/cube.wrl @@ -0,0 +1,167 @@ +#VRML V2.0 utf8 + +WorldInfo { + info [ + "Generated by Open CASCADE Technology 7.7" + ] +} +DEF Cube Group { + children [ + Shape { + geometry IndexedFaceSet { + solid FALSE + convex FALSE + coord Coordinate { + point [ + 0 0 0, + 0 0 10, + 0 10 0, + 0 10 10 + ] + } + coordIndex [ + 1,2,0, -1, + 1,3,2, -1 + ] + normal Normal { + vector [ + -1 -0 0, + -1 -0 0, + -1 -0 0, + -1 -0 0 + ] + } + } + } + Shape { + geometry IndexedFaceSet { + solid FALSE + convex FALSE + coord Coordinate { + point [ + 10 0 0, + 10 0 10, + 10 10 0, + 10 10 10 + ] + } + coordIndex [ + 1,0,2, -1, + 1,2,3, -1 + ] + normal Normal { + vector [ + 1 0 -0, + 1 0 -0, + 1 0 -0, + 1 0 -0 + ] + } + } + } + Shape { + geometry IndexedFaceSet { + solid FALSE + convex FALSE + coord Coordinate { + point [ + 0 0 0, + 10 0 0, + 0 0 10, + 10 0 10 + ] + } + coordIndex [ + 3,0,1, -1, + 3,2,0, -1 + ] + normal Normal { + vector [ + -0 -1 -0, + -0 -1 -0, + -0 -1 -0, + -0 -1 -0 + ] + } + } + } + Shape { + geometry IndexedFaceSet { + solid FALSE + convex FALSE + coord Coordinate { + point [ + 0 10 0, + 10 10 0, + 0 10 10, + 10 10 10 + ] + } + coordIndex [ + 3,1,0, -1, + 3,0,2, -1 + ] + normal Normal { + vector [ + 0 1 0, + 0 1 0, + 0 1 0, + 0 1 0 + ] + } + } + } + Shape { + geometry IndexedFaceSet { + solid FALSE + convex FALSE + coord Coordinate { + point [ + 0 0 0, + 0 10 0, + 10 0 0, + 10 10 0 + ] + } + coordIndex [ + 3,0,1, -1, + 3,2,0, -1 + ] + normal Normal { + vector [ + -0 -0 -1, + -0 -0 -1, + -0 -0 -1, + -0 -0 -1 + ] + } + } + } + Shape { + geometry IndexedFaceSet { + solid FALSE + convex FALSE + coord Coordinate { + point [ + 0 0 10, + 0 10 10, + 10 0 10, + 10 10 10 + ] + } + coordIndex [ + 3,1,0, -1, + 3,0,2, -1 + ] + normal Normal { + vector [ + 0 0 1, + 0 0 1, + 0 0 1, + 0 0 1 + ] + } + } + } + ] +} diff --git a/tests/test_base.cpp b/tests/test_base.cpp index ee3baccf..44aef516 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -462,6 +462,7 @@ void TestBase::IO_probeFormat_test_data() QTest::newRow("cube.obj") << "tests/inputs/cube.obj" << IO::Format_OBJ; QTest::newRow("cube.ply") << "tests/inputs/cube.ply" << IO::Format_PLY; QTest::newRow("cube.off") << "tests/inputs/cube.off" << IO::Format_OFF; + QTest::newRow("cube.wrl") << "tests/inputs/cube.wrl" << IO::Format_VRML; } void TestBase::IO_probeFormatDirect_test() From edd91b3d3bb36d13897e172e702c73c2df75f3b3 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 11 Apr 2024 10:21:28 +0200 Subject: [PATCH 049/101] IO_OCC: indentation fixes --- src/io_occ/io_occ_gltf_reader.cpp | 11 +++++++---- src/io_occ/io_occ_gltf_reader.h | 2 +- src/io_occ/io_occ_obj_reader.cpp | 6 ++++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/io_occ/io_occ_gltf_reader.cpp b/src/io_occ/io_occ_gltf_reader.cpp index 9e500629..3991ecce 100644 --- a/src/io_occ/io_occ_gltf_reader.cpp +++ b/src/io_occ/io_occ_gltf_reader.cpp @@ -16,13 +16,16 @@ class OccGltfReader::Properties : public OccBaseMeshReaderProperties { Properties(PropertyGroup* parentGroup) : OccBaseMeshReaderProperties(parentGroup) { - this->skipEmptyNodes.setDescription( - textIdTr("Ignore nodes without geometry(`Yes` by default)")); + this->skipEmptyNodes.setDescription( + textIdTr("Ignore nodes without geometry(`Yes` by default)") + ); this->useMeshNameAsFallback.setDescription( - textIdTr("Use mesh name in case if node name is empty(`Yes` by default)")); + textIdTr("Use mesh name in case if node name is empty(`Yes` by default)") + ); } - void restoreDefaults() override { + void restoreDefaults() override + { OccBaseMeshReaderProperties::restoreDefaults(); this->skipEmptyNodes.setValue(true); this->useMeshNameAsFallback.setValue(true); diff --git a/src/io_occ/io_occ_gltf_reader.h b/src/io_occ/io_occ_gltf_reader.h index 0670ce38..c6e4b290 100644 --- a/src/io_occ/io_occ_gltf_reader.h +++ b/src/io_occ/io_occ_gltf_reader.h @@ -36,7 +36,7 @@ class OccGltfReader : public OccBaseMeshReader { private: class Properties; Parameters m_params; - mutable RWGltf_CafReader m_reader; + RWGltf_CafReader m_reader; }; } // namespace IO diff --git a/src/io_occ/io_occ_obj_reader.cpp b/src/io_occ/io_occ_obj_reader.cpp index 590ce8ad..e495773a 100644 --- a/src/io_occ/io_occ_obj_reader.cpp +++ b/src/io_occ/io_occ_obj_reader.cpp @@ -17,10 +17,12 @@ class OccObjReader::Properties : public OccBaseMeshReaderProperties { : OccBaseMeshReaderProperties(parentGroup) { this->singlePrecisionVertexCoords.setDescription( - textId("Single precision flag for reading vertex data(coordinates)").tr()); + textId("Single precision flag for reading vertex data(coordinates)").tr() + ); } - void restoreDefaults() override { + void restoreDefaults() override + { OccBaseMeshReaderProperties::restoreDefaults(); this->singlePrecisionVertexCoords.setValue(false); } From a539521792d779087b1cf2df33c37637558ab853 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 11 Apr 2024 10:22:10 +0200 Subject: [PATCH 050/101] IO_OCC: fix weird "mirroring" after VRML loading --- src/io_occ/io_occ_vrml_reader.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/io_occ/io_occ_vrml_reader.cpp b/src/io_occ/io_occ_vrml_reader.cpp index fc448e3d..30eaa79c 100644 --- a/src/io_occ/io_occ_vrml_reader.cpp +++ b/src/io_occ/io_occ_vrml_reader.cpp @@ -12,6 +12,14 @@ namespace IO { OccVrmlReader::OccVrmlReader() : OccBaseMeshReader(m_reader) { + // Fixes weird "mirroring" of the loaded model + m_reader.SetFileLengthUnit(1.); +#if 0 + double scaleFactor = 1.; + if (!XCAFDoc_DocumentTool::GetLengthUnit(doc, scaleFactor)) + scaleFactor = UnitsMethods::GetCasCadeLengthUnit(); + m_reader.SetSystemLengthUnit(scaleFactor); +#endif } std::unique_ptr OccVrmlReader::createProperties(PropertyGroup* parentGroup) From fb9335fc8ad57cc36f15e9a09207b9ee91f65a88 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 11 Apr 2024 17:02:57 +0200 Subject: [PATCH 051/101] App: fix hidden unit combo box in Measure panel Relates to GitHub #270 --- src/app/widget_measure.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/widget_measure.cpp b/src/app/widget_measure.cpp index 570af0e4..5b8aea17 100644 --- a/src/app/widget_measure.cpp +++ b/src/app/widget_measure.cpp @@ -204,14 +204,16 @@ void WidgetMeasure::onMeasureTypeChanged(int id) const bool measureIsAngle = measureType == MeasureType::Angle; const bool measureIsArea = measureType == MeasureType::Area; const bool measureIsVolume = measureType == MeasureType::BoundingBox; + // Note: don't call "ui->comboUnit->setVisible(labelUnit->isVisible())" because at this point + // QWidget::isVisible() might not be effective(probably needs to process eventloop) m_ui->label_LengthUnit->setVisible(measureIsLengthBased && !measureIsArea); - m_ui->combo_LengthUnit->setVisible(m_ui->label_LengthUnit->isVisible()); + m_ui->combo_LengthUnit->setVisible(measureIsLengthBased && !measureIsArea); m_ui->label_AngleUnit->setVisible(measureIsAngle); - m_ui->combo_AngleUnit->setVisible(m_ui->label_AngleUnit->isVisible()); + m_ui->combo_AngleUnit->setVisible(measureIsAngle); m_ui->label_AreaUnit->setVisible(measureIsArea); - m_ui->combo_AreaUnit->setVisible(m_ui->label_AreaUnit->isVisible()); + m_ui->combo_AreaUnit->setVisible(measureIsArea); m_ui->label_VolumeUnit->setVisible(measureIsVolume); - m_ui->combo_VolumeUnit->setVisible(m_ui->label_VolumeUnit->isVisible()); + m_ui->combo_VolumeUnit->setVisible(measureIsVolume); auto gfxScene = m_guiDoc->graphicsScene(); From ffc8cb0b1d65e4cae9094019558ccc8b97628574 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 12 Apr 2024 10:08:45 +0200 Subject: [PATCH 052/101] Base: apply rule "thread_local implies static" in CafUtils::labelTag() --- src/base/caf_utils.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/base/caf_utils.cpp b/src/base/caf_utils.cpp index 1741c403..2a03d760 100644 --- a/src/base/caf_utils.cpp +++ b/src/base/caf_utils.cpp @@ -13,7 +13,9 @@ namespace Mayo { const TCollection_AsciiString& CafUtils::labelTag(const TDF_Label& label) { - static thread_local TCollection_AsciiString entry; + // Note: thread_local implies "static" + // See https://en.cppreference.com/w/cpp/language/storage_duration + thread_local TCollection_AsciiString entry; TDF_Tool::Entry(label, entry); return entry; } From 185694d29dbbef1c07bddbdf618204a97e0b6b53 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 18 Apr 2024 17:22:27 +0200 Subject: [PATCH 053/101] App: fix support of TGA texture files in "Inspect XDE" tool Relates to GitHub #272 --- src/app/dialog_inspect_xde.cpp | 52 ++++++++++++++++++++++++++-------- src/app/qtgui_utils.cpp | 1 + 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/app/dialog_inspect_xde.cpp b/src/app/dialog_inspect_xde.cpp index d4c0e85f..0edcc896 100644 --- a/src/app/dialog_inspect_xde.cpp +++ b/src/app/dialog_inspect_xde.cpp @@ -21,6 +21,7 @@ #include "qtwidgets_utils.h" #include "ui_dialog_inspect_xde.h" +#include #include #include #include @@ -295,6 +296,42 @@ static void loadLabelMaterialProperties( #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) +// Load pixmap from file +static QPixmap loadPixmap(const FilePath& filePath) +{ + QPixmap pixmap; + bool okLoad = pixmap.load(filepathTo(filePath)); + if (!okLoad || pixmap.isNull()) { + // QPixmap::load() failed, try with OpenCascade Image_AlienPixMap::Load() + Image_AlienPixMap occPixmap; + okLoad = occPixmap.Load(filepathTo(filePath)); + if (okLoad) + pixmap = QtGuiUtils::toQPixmap(occPixmap); + } + + return pixmap; +} + +// Load pixmap from data buffer +static QPixmap loadPixmap(const QByteArray& fileData) +{ + QPixmap pixmap; + bool okLoad = pixmap.loadFromData(fileData); + if (!okLoad || pixmap.isNull()) { + // QPixmap::loadFromData() failed, try with OpenCascade Image_AlienPixMap::Load() + Image_AlienPixMap occPixmap; + okLoad = occPixmap.Load( + reinterpret_cast(fileData.constData()), + fileData.size(), + TCollection_AsciiString{} + ); + if (okLoad) + pixmap = QtGuiUtils::toQPixmap(occPixmap); + } + + return pixmap; +} + // Provides a QTreeWidgetItem specialized to display an image file with a tooltip // QTreeWidgetItem::setToolTip() could be used but it forces all image files to be loaded on // tree item construction @@ -324,18 +361,9 @@ class ImageFileTreeWidgetItem : public QTreeWidgetItem { return {}; if (ptrItem->strToolTip.isEmpty()) { - QPixmap pixmap; - uintmax_t imageSize = 0; - - if (!ptrItem->filePath.empty()) { - pixmap.load(filepathTo(ptrItem->filePath)); - imageSize = filepathFileSize(ptrItem->filePath); - } - else { - pixmap.loadFromData(ptrItem->fileData); - imageSize = ptrItem->fileData.size(); - } - + const bool isFilePathDefined = !ptrItem->filePath.empty(); + const QPixmap pixmap = isFilePathDefined ? loadPixmap(ptrItem->filePath) : loadPixmap(ptrItem->fileData); + const uintmax_t imageSize = isFilePathDefined ? filepathFileSize(ptrItem->filePath) : ptrItem->fileData.size(); if (!pixmap.isNull()) { QBuffer bufferPixmap; const int pixmapWidth = std::min(pixmap.width(), int(400 * qGuiApp->devicePixelRatio())); diff --git a/src/app/qtgui_utils.cpp b/src/app/qtgui_utils.cpp index d5690f74..dc4ad1cf 100644 --- a/src/app/qtgui_utils.cpp +++ b/src/app/qtgui_utils.cpp @@ -203,6 +203,7 @@ QPixmap toQPixmap(const Image_PixMap& pixmap) auto fnToQImageFormat = [](Image_Format occFormat) { switch (occFormat) { case Image_Format_RGB: return QImage::Format_RGB888; + case Image_Format_BGR: return QImage::Format_BGR888; case Image_Format_RGBA: return QImage::Format_ARGB32; case Image_Format_RGBF: return QImage::Format_RGB444; case Image_Format_Gray: return QImage::Format_Grayscale8; From f051a9a1bfc8841cf020d927f36aa2adab431b1c Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 18 Apr 2024 17:57:53 +0200 Subject: [PATCH 054/101] IO_Assimp: support DirectX file format Relates to GitHub #273 --- src/base/io_format.cpp | 4 ++++ src/base/io_format.h | 1 + src/io_assimp/io_assimp.cpp | 9 ++++++++- src/io_assimp/io_assimp_reader.cpp | 2 ++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/base/io_format.cpp b/src/base/io_format.cpp index b7505b3d..fdbee9c8 100644 --- a/src/base/io_format.cpp +++ b/src/base/io_format.cpp @@ -32,6 +32,7 @@ std::string_view formatIdentifier(Format format) case Format_COLLADA: return "COLLADA"; case Format_FBX: return "FBX"; case Format_X3D: return "X3D"; + case Format_DirectX: return "X"; case Format_Blender: return "Blender"; } @@ -59,6 +60,7 @@ std::string_view formatName(Format format) case Format_COLLADA: return "COLLAborative Design Activity(ISO/PAS 17506)"; case Format_FBX: return "Filmbox"; case Format_X3D: return "Extensible 3D Graphics(ISO/IEC 19775/19776/19777)"; + case Format_DirectX: return "DirectX File Format"; case Format_Blender: return "Blender File Format"; } @@ -84,6 +86,7 @@ Span formatFileSuffixes(Format format) static std::string_view suffix_stl[] = { "stl" }; static std::string_view suffix_vrml[] = { "wrl", "wrz", "vrml" }; static std::string_view suffix_x3d[] = { "x3d", "x3dv", "x3db", "x3dz", "x3dbz", "x3dvz" }; + static std::string_view suffix_directx[] = { "x" }; static std::string_view suffix_blender[] = { "blend", "blender", "blend1", "blend2" }; switch (format) { @@ -105,6 +108,7 @@ Span formatFileSuffixes(Format format) case Format_STL: return suffix_stl; case Format_VRML: return suffix_vrml; case Format_X3D: return suffix_x3d; + case Format_DirectX: return suffix_directx; case Format_Blender: return suffix_blender; } diff --git a/src/base/io_format.h b/src/base/io_format.h index 081e746e..d1fed8c4 100644 --- a/src/base/io_format.h +++ b/src/base/io_format.h @@ -32,6 +32,7 @@ enum Format { Format_STL, Format_VRML, Format_X3D, + Format_DirectX, Format_Blender }; diff --git a/src/io_assimp/io_assimp.cpp b/src/io_assimp/io_assimp.cpp index 8c03831c..0cf81af8 100644 --- a/src/io_assimp/io_assimp.cpp +++ b/src/io_assimp/io_assimp.cpp @@ -15,7 +15,14 @@ namespace IO { Span AssimpFactoryReader::formats() const { static const Format array[] = { - Format_AMF, Format_3DS, Format_3MF, Format_COLLADA, Format_FBX, Format_X3D, Format_Blender + Format_AMF, + Format_3DS, + Format_3MF, + Format_COLLADA, + Format_FBX, + Format_X3D, + Format_Blender, + Format_DirectX }; return array; } diff --git a/src/io_assimp/io_assimp_reader.cpp b/src/io_assimp/io_assimp_reader.cpp index 01400547..92a9ae82 100644 --- a/src/io_assimp/io_assimp_reader.cpp +++ b/src/io_assimp/io_assimp_reader.cpp @@ -524,8 +524,10 @@ Handle(XCAFDoc_VisMaterial) AssimpReader::createOccVisMaterial( this->messenger()->trace() << "AI_MATKEY_SPECULAR_FACTOR: " << factor; #endif +#if 0 // Emmissive color disabled as it's making models in Mayo to be rendered nearly with white color if (fnGetColor4D(AI_MATKEY_COLOR_EMISSIVE, &color, &matCommon.IsDefined)) matCommon.EmissiveColor = toOccColor(color); +#endif } { From 0e8cadf1f6c0a462d7ec25b5d4e750091496ed75 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 18 Apr 2024 18:23:53 +0200 Subject: [PATCH 055/101] Update README.md[skip ci] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ef9ff89c..addb651d 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ OFF | :white_check_mark: | :white_check_mark: | FBX | :white_check_mark: | :x: | Collada | :white_check_mark: | :x: | X3D | :white_check_mark: | :x: | +X(DirectX)| :white_check_mark: | :x: | Image | :x: | :white_check_mark: | PNG, JPEG, ... See also this dedicated [wikipage](https://github.com/fougue/mayo/wiki/Supported-formats) for more details From e87d023cde9bffe559d3b3bde29d4555faf17d85 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 22 Apr 2024 18:33:43 +0200 Subject: [PATCH 056/101] Qrc: move mayo.qrc file into images/ folder --- CMakeLists.txt | 4 ++- images/mayo.qrc | 86 +++++++++++++++++++++++++++++++++++++++++++++++ mayo.qrc | 88 ------------------------------------------------- 3 files changed, 89 insertions(+), 89 deletions(-) create mode 100644 images/mayo.qrc delete mode 100644 mayo.qrc diff --git a/CMakeLists.txt b/CMakeLists.txt index d23db60e..5e0558b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -546,7 +546,9 @@ if(Mayo_BuildApp) ${MayoApp_SourceFiles} ${MayoApp_HeaderFiles} ${MayoApp_QtUiFiles} - mayo.qrc i18n/qt${QT_VERSION_MAJOR}base.qrc + i18n/mayo.qrc + i18n/qt${QT_VERSION_MAJOR}base.qrc + images/mayo.qrc ${Mayo_RcIconsWin} ${Mayo_AppIconMacOS} ) diff --git a/images/mayo.qrc b/images/mayo.qrc new file mode 100644 index 00000000..6ac3e234 --- /dev/null +++ b/images/mayo.qrc @@ -0,0 +1,86 @@ + + + appicon_128.png + appicon_16.png + appicon_24.png + appicon_32.png + appicon_64.png + graphics/opencascade_hatch_1.png + themes/classic/back-square.svg + themes/classic/back.svg + themes/classic/camera.svg + themes/classic/clip-plane.svg + themes/classic/cross.svg + themes/classic/expand.svg + themes/classic/export.svg + themes/classic/file.svg + themes/classic/gear.svg + themes/classic/import.svg + themes/classic/indicator-down-disabled_8.png + themes/classic/indicator-down_8.png + themes/classic/item-mesh.svg + themes/classic/item-xde.svg + themes/classic/left-sidebar.svg + themes/classic/link.svg + themes/classic/next.svg + themes/classic/stop.svg + themes/classic/view-back.svg + themes/classic/view-bottom.svg + themes/classic/view-front.svg + themes/classic/view-iso.svg + themes/classic/view-left.svg + themes/classic/view-right.svg + themes/classic/view-top.svg + themes/classic/xde-assembly.svg + themes/classic/zoom-in.svg + themes/classic/zoom-out.svg + themes/dark/back-square.svg + themes/dark/back.svg + themes/dark/camera.svg + themes/dark/clip-plane.svg + themes/dark/cross.svg + themes/dark/expand.svg + themes/dark/export.svg + themes/dark/file.svg + themes/dark/gear.svg + themes/dark/import.svg + themes/dark/indicator-down-disabled_8.png + themes/dark/indicator-down_8.png + themes/dark/item-mesh.svg + themes/dark/item-xde.svg + themes/dark/left-sidebar.svg + themes/dark/link.svg + themes/dark/next.svg + themes/dark/stop.svg + themes/dark/view-back.svg + themes/dark/view-bottom.svg + themes/dark/view-front.svg + themes/dark/view-iso.svg + themes/dark/view-left.svg + themes/dark/view-right.svg + themes/dark/view-top.svg + themes/dark/xde-assembly.svg + themes/dark/xde-simple-shape.svg + themes/dark/zoom-in.svg + themes/dark/zoom-out.svg + widget_pseudo_fold.png + xde_reference_16.png + themes/classic/xde-simple-shape.svg + themes/classic/edit.svg + themes/dark/edit.svg + themes/classic/add-file.svg + themes/classic/open-files.svg + themes/classic/multiple.svg + themes/dark/multiple.svg + themes/classic/reload.svg + themes/dark/reload.svg + themes/classic/measure.svg + themes/dark/measure.svg + themes/classic/turn-ccw.svg + themes/classic/turn-cw.svg + themes/dark/turn-ccw.svg + themes/dark/turn-cw.svg + themes/classic/grid.svg + themes/dark/grid.svg + + diff --git a/mayo.qrc b/mayo.qrc deleted file mode 100644 index b446ce4f..00000000 --- a/mayo.qrc +++ /dev/null @@ -1,88 +0,0 @@ - - - images/appicon_128.png - images/appicon_16.png - images/appicon_24.png - images/appicon_32.png - images/appicon_64.png - images/graphics/opencascade_hatch_1.png - images/themes/classic/back-square.svg - images/themes/classic/back.svg - images/themes/classic/camera.svg - images/themes/classic/clip-plane.svg - images/themes/classic/cross.svg - images/themes/classic/expand.svg - images/themes/classic/export.svg - images/themes/classic/file.svg - images/themes/classic/gear.svg - images/themes/classic/import.svg - images/themes/classic/indicator-down-disabled_8.png - images/themes/classic/indicator-down_8.png - images/themes/classic/item-mesh.svg - images/themes/classic/item-xde.svg - images/themes/classic/left-sidebar.svg - images/themes/classic/link.svg - images/themes/classic/next.svg - images/themes/classic/stop.svg - images/themes/classic/view-back.svg - images/themes/classic/view-bottom.svg - images/themes/classic/view-front.svg - images/themes/classic/view-iso.svg - images/themes/classic/view-left.svg - images/themes/classic/view-right.svg - images/themes/classic/view-top.svg - images/themes/classic/xde-assembly.svg - images/themes/classic/zoom-in.svg - images/themes/classic/zoom-out.svg - images/themes/dark/back-square.svg - images/themes/dark/back.svg - images/themes/dark/camera.svg - images/themes/dark/clip-plane.svg - images/themes/dark/cross.svg - images/themes/dark/expand.svg - images/themes/dark/export.svg - images/themes/dark/file.svg - images/themes/dark/gear.svg - images/themes/dark/import.svg - images/themes/dark/indicator-down-disabled_8.png - images/themes/dark/indicator-down_8.png - images/themes/dark/item-mesh.svg - images/themes/dark/item-xde.svg - images/themes/dark/left-sidebar.svg - images/themes/dark/link.svg - images/themes/dark/next.svg - images/themes/dark/stop.svg - images/themes/dark/view-back.svg - images/themes/dark/view-bottom.svg - images/themes/dark/view-front.svg - images/themes/dark/view-iso.svg - images/themes/dark/view-left.svg - images/themes/dark/view-right.svg - images/themes/dark/view-top.svg - images/themes/dark/xde-assembly.svg - images/themes/dark/xde-simple-shape.svg - images/themes/dark/zoom-in.svg - images/themes/dark/zoom-out.svg - images/widget_pseudo_fold.png - images/xde_reference_16.png - images/themes/classic/xde-simple-shape.svg - images/themes/classic/edit.svg - images/themes/dark/edit.svg - i18n/mayo_en.qm - i18n/mayo_fr.qm - images/themes/classic/add-file.svg - images/themes/classic/open-files.svg - images/themes/classic/multiple.svg - images/themes/dark/multiple.svg - images/themes/classic/reload.svg - images/themes/dark/reload.svg - images/themes/classic/measure.svg - images/themes/dark/measure.svg - images/themes/classic/turn-ccw.svg - images/themes/classic/turn-cw.svg - images/themes/dark/turn-ccw.svg - images/themes/dark/turn-cw.svg - images/themes/classic/grid.svg - images/themes/dark/grid.svg - - From 637058ad2008b23458db88be534d1199db4d6e7e Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Wed, 24 Apr 2024 10:25:14 +0200 Subject: [PATCH 057/101] CI: use Mayo_PostBuildCopyRuntimeDLLs cmake option on Windows --- .github/workflows/ci_windows.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci_windows.yml b/.github/workflows/ci_windows.yml index 78527c47..83fac0a8 100644 --- a/.github/workflows/ci_windows.yml +++ b/.github/workflows/ci_windows.yml @@ -83,6 +83,7 @@ jobs: cmake .. ^ -DMayo_BuildTests=ON ^ -DMayo_BuildPluginAssimp=ON ^ + -DMayo_PostBuildCopyRuntimeDLLs=ON ^ -DOpenCASCADE_DIR=../OpenCASCADE-${{matrix.occ_version}}-vc14-64/opencascade-${{matrix.occ_version}} ^ -Dassimp_DIR=${{github.workspace}}/assimp-5.3.1/lib/cmake/assimp-5.3 cmake --build . ^ @@ -93,7 +94,5 @@ jobs: working-directory: ${{github.workspace}}/build shell: cmd run: | - call ..\OpenCASCADE-${{matrix.occ_version}}-vc14-64\opencascade-${{matrix.occ_version}}\env.bat - set PATH=${{github.workspace}}\assimp-5.3.1\bin;%PATH% release\mayo.exe --runtests -o utests-output.txt more utests-output.txt From 4d7f95b38282c2e88db1b208c83c5988bfc673b2 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 25 Apr 2024 12:36:53 +0200 Subject: [PATCH 058/101] CMake: silent GCC12 warnings for deprecated std::iterator --- CMakeLists.txt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e0558b8..c5449b89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -300,10 +300,16 @@ endif() if(OpenCASCADE_FOUND) message(STATUS "OpenCascade version ${OpenCASCADE_VERSION}") - # Silent warnings C4996 and STL4015: the std::iterator class template(used as a base class to - # provide typedefs) is deprecated in C++17 - if(MSVC AND OpenCASCADE_VERSION VERSION_LESS 7.7.0) - list(APPEND Mayo_CompileDefinitions _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING) + # std::iterator class template(used as a base class to provide typedefs) is deprecated in C++17 + # OpenCascade < 7.7.0 uses std::iterator for NCollection_StlIterator and this is causing many + # deprecation warnings + if(OpenCASCADE_VERSION VERSION_LESS 7.7.0) + if(MSVC) + # Silent warnings C4996 and STL4015 + list(APPEND Mayo_CompileDefinitions _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING) + elseif((CMAKE_CXX_COMPILER_ID MATCHES "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12)) + list(APPEND Mayo_CompileOptions -Wno-deprecated-declarations) + endif() endif() # Add OpenCASCADE libraries From c4fdeb206235e9c37f34b18f5463d1e0702b2c14 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 25 Apr 2024 12:37:09 +0200 Subject: [PATCH 059/101] Base: fix missing header --- src/base/property.cpp | 2 ++ src/base/property_enumeration.cpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/base/property.cpp b/src/base/property.cpp index d98acb31..61b9c623 100644 --- a/src/base/property.cpp +++ b/src/base/property.cpp @@ -7,6 +7,8 @@ #include "property.h" #include "property_enumeration.h" + +#include #include namespace Mayo { diff --git a/src/base/property_enumeration.cpp b/src/base/property_enumeration.cpp index 556c3e8e..1ef57091 100644 --- a/src/base/property_enumeration.cpp +++ b/src/base/property_enumeration.cpp @@ -6,12 +6,14 @@ #include "property_enumeration.h" +#include #include namespace Mayo { PropertyEnumeration::PropertyEnumeration( - PropertyGroup* grp, const TextId& name, const Enumeration* enumeration) + PropertyGroup* grp, const TextId& name, const Enumeration* enumeration + ) : Property(grp, name), m_enumeration(enumeration), m_value(enumeration && enumeration->empty() ? enumeration->itemAt(0).value : -1) From 940d006b52d097cc4634dd19734836f62e61bcaa Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Thu, 25 Apr 2024 15:16:52 +0200 Subject: [PATCH 060/101] 3rdparty: update magic_enum to latest --- src/3rdparty/commit_magic_enum.txt | 2 +- src/3rdparty/magic_enum/magic_enum.hpp | 1787 ++++++++++++++---------- 2 files changed, 1078 insertions(+), 711 deletions(-) diff --git a/src/3rdparty/commit_magic_enum.txt b/src/3rdparty/commit_magic_enum.txt index d5682268..80ff3874 100644 --- a/src/3rdparty/commit_magic_enum.txt +++ b/src/3rdparty/commit_magic_enum.txt @@ -1 +1 @@ -caa2f9e6ef882671bc8be84dd98a225ad12d62bf \ No newline at end of file +ec11ae0c285773e691a1e8372e4984189a6bed1e diff --git a/src/3rdparty/magic_enum/magic_enum.hpp b/src/3rdparty/magic_enum/magic_enum.hpp index ac163058..d3c3ab64 100644 --- a/src/3rdparty/magic_enum/magic_enum.hpp +++ b/src/3rdparty/magic_enum/magic_enum.hpp @@ -5,11 +5,11 @@ // | | | | (_| | (_| | | (__ | |____| | | | |_| | | | | | | | |____|_| |_| // |_| |_|\__,_|\__, |_|\___| |______|_| |_|\__,_|_| |_| |_| \_____| // __/ | https://github.com/Neargye/magic_enum -// |___/ version 0.7.0 +// |___/ version 0.9.5 // // Licensed under the MIT License . // SPDX-License-Identifier: MIT -// Copyright (c) 2019 - 2020 Daniil Goncharov . +// Copyright (c) 2019 - 2024 Daniil Goncharov . // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -33,40 +33,57 @@ #define NEARGYE_MAGIC_ENUM_HPP #define MAGIC_ENUM_VERSION_MAJOR 0 -#define MAGIC_ENUM_VERSION_MINOR 7 -#define MAGIC_ENUM_VERSION_PATCH 0 +#define MAGIC_ENUM_VERSION_MINOR 9 +#define MAGIC_ENUM_VERSION_PATCH 5 #include -#include -#include #include -#include +#include +#include #include #include #include +#if defined(MAGIC_ENUM_CONFIG_FILE) +# include MAGIC_ENUM_CONFIG_FILE +#endif + #if !defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) -#include +# include #endif #if !defined(MAGIC_ENUM_USING_ALIAS_STRING) -#include +# include #endif #if !defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) -#include +# include +#endif + +#if defined(MAGIC_ENUM_NO_ASSERT) +# define MAGIC_ENUM_ASSERT(...) static_cast(0) +#elif !defined(MAGIC_ENUM_ASSERT) +# include +# define MAGIC_ENUM_ASSERT(...) assert((__VA_ARGS__)) #endif #if defined(__clang__) # pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunknown-warning-option" +# pragma clang diagnostic ignored "-Wenum-constexpr-conversion" +# pragma clang diagnostic ignored "-Wuseless-cast" // suppresses 'static_cast('\0')' for char_type = char (common on Linux). #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // May be used uninitialized 'return {};'. +# pragma GCC diagnostic ignored "-Wuseless-cast" // suppresses 'static_cast('\0')' for char_type = char (common on Linux). #elif defined(_MSC_VER) # pragma warning(push) -# pragma warning(disable : 26495) // Variable 'static_string::chars' is uninitialized. +# pragma warning(disable : 26495) // Variable 'static_str::chars_' is uninitialized. +# pragma warning(disable : 28020) // Arithmetic overflow: Using operator '-' on a 4 byte value and then casting the result to a 8 byte value. +# pragma warning(disable : 26451) // The expression '0<=_Param_(1)&&_Param_(1)<=1-1' is not true at this call. +# pragma warning(disable : 4514) // Unreferenced inline function has been removed. #endif // Checks magic_enum compiler compatibility. -#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910 +#if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1910 || defined(__RESHARPER__) # undef MAGIC_ENUM_SUPPORTED # define MAGIC_ENUM_SUPPORTED 1 #endif @@ -86,7 +103,20 @@ // Enum value must be less or equals than MAGIC_ENUM_RANGE_MAX. By default MAGIC_ENUM_RANGE_MAX = 128. // If need another max range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MAX. #if !defined(MAGIC_ENUM_RANGE_MAX) -# define MAGIC_ENUM_RANGE_MAX 128 +# define MAGIC_ENUM_RANGE_MAX 127 +#endif + +// Improve ReSharper C++ intellisense performance with builtins, avoiding unnecessary template instantiations. +#if defined(__RESHARPER__) +# undef MAGIC_ENUM_GET_ENUM_NAME_BUILTIN +# undef MAGIC_ENUM_GET_TYPE_NAME_BUILTIN +# if __RESHARPER__ >= 20230100 +# define MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V) __rscpp_enumerator_name(V) +# define MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(T) __rscpp_type_name() +# else +# define MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V) nullptr +# define MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(T) nullptr +# endif #endif namespace magic_enum { @@ -95,24 +125,40 @@ namespace magic_enum { #if defined(MAGIC_ENUM_USING_ALIAS_OPTIONAL) MAGIC_ENUM_USING_ALIAS_OPTIONAL #else -template -using optional = std::optional; +using std::optional; #endif -// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW. +// If need another string_view type, define the macro MAGIC_ENUM_USING_ALIAS_STRING_VIEW. #if defined(MAGIC_ENUM_USING_ALIAS_STRING_VIEW) -MAGIC_ENUM_USING_ALIAS_STRING_VIEW + MAGIC_ENUM_USING_ALIAS_STRING_VIEW #else -using string_view = std::string_view; +using std::string_view; #endif -// If need another optional type, define the macro MAGIC_ENUM_USING_ALIAS_STRING. +// If need another string type, define the macro MAGIC_ENUM_USING_ALIAS_STRING. #if defined(MAGIC_ENUM_USING_ALIAS_STRING) -MAGIC_ENUM_USING_ALIAS_STRING + MAGIC_ENUM_USING_ALIAS_STRING #else -using string = std::string; +using std::string; #endif + using char_type = string_view::value_type; +static_assert(std::is_same_v, "magic_enum::customize requires same string_view::value_type and string::value_type"); +static_assert([] { + if constexpr (std::is_same_v) { + constexpr const char c[] = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789|"; + constexpr const wchar_t wc[] = L"abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789|"; + static_assert(std::size(c) == std::size(wc), "magic_enum::customize identifier characters are multichars in wchar_t."); + + for (std::size_t i = 0; i < std::size(c); ++i) { + if (c[i] != wc[i]) { + return false; + } + } + } + return true; +} (), "magic_enum::customize wchar_t is not compatible with ASCII."); + namespace customize { // Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]. By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128. @@ -120,26 +166,46 @@ namespace customize { // If need another range for specific enum type, add specialization enum_range for necessary enum type. template struct enum_range { - static_assert(std::is_enum_v, "magic_enum::customize::enum_range requires enum type."); - inline static constexpr int min = MAGIC_ENUM_RANGE_MIN; - inline static constexpr int max = MAGIC_ENUM_RANGE_MAX; - static_assert(max > min, "magic_enum::customize::enum_range requires max > min."); + static constexpr int min = MAGIC_ENUM_RANGE_MIN; + static constexpr int max = MAGIC_ENUM_RANGE_MAX; }; -static_assert(MAGIC_ENUM_RANGE_MIN <= 0, "MAGIC_ENUM_RANGE_MIN must be less or equals than 0."); -static_assert(MAGIC_ENUM_RANGE_MIN > (std::numeric_limits::min)(), "MAGIC_ENUM_RANGE_MIN must be greater than INT16_MIN."); +static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN."); + +namespace detail { -static_assert(MAGIC_ENUM_RANGE_MAX > 0, "MAGIC_ENUM_RANGE_MAX must be greater than 0."); -static_assert(MAGIC_ENUM_RANGE_MAX < (std::numeric_limits::max)(), "MAGIC_ENUM_RANGE_MAX must be less than INT16_MAX."); +enum class customize_tag { + default_tag, + invalid_tag, + custom_tag +}; -static_assert(MAGIC_ENUM_RANGE_MAX > MAGIC_ENUM_RANGE_MIN, "MAGIC_ENUM_RANGE_MAX must be greater than MAGIC_ENUM_RANGE_MIN."); +} // namespace magic_enum::customize::detail -// If need cunstom names for enum type, add specialization enum_name for necessary enum type. +class customize_t : public std::pair { +public: + constexpr customize_t(string_view srt) : std::pair{detail::customize_tag::custom_tag, srt} {} + constexpr customize_t(const char_type* srt) : customize_t{string_view{srt}} {} + constexpr customize_t(detail::customize_tag tag) : std::pair{tag, string_view{}} { + MAGIC_ENUM_ASSERT(tag != detail::customize_tag::custom_tag); + } +}; + +// Default customize. +inline constexpr auto default_tag = customize_t{detail::customize_tag::default_tag}; +// Invalid customize. +inline constexpr auto invalid_tag = customize_t{detail::customize_tag::invalid_tag}; + +// If need custom names for enum, add specialization enum_name for necessary enum type. template -constexpr string_view enum_name(E) noexcept { - static_assert(std::is_enum_v, "magic_enum::customize::enum_name requires enum type."); +constexpr customize_t enum_name(E) noexcept { + return default_tag; +} - return {}; +// If need custom type name for enum, add specialization enum_type_name for necessary enum type. +template +constexpr customize_t enum_type_name() noexcept { + return default_tag; } } // namespace magic_enum::customize @@ -154,422 +220,675 @@ struct supported : std::false_type {}; #endif -template -struct static_string { - constexpr explicit static_string(string_view str) noexcept : static_string{str, std::make_index_sequence{}} { - assert(str.size() == N); - } +template , std::enable_if_t, int> = 0> +using enum_constant = std::integral_constant; + +template +inline constexpr bool always_false_v = false; + +template +struct has_is_flags : std::false_type {}; + +template +struct has_is_flags::is_flags)>> : std::bool_constant::is_flags)>>> {}; - constexpr const char* data() const noexcept { return chars.data(); } +template +struct range_min : std::integral_constant {}; - constexpr std::size_t size() const noexcept { return N; } +template +struct range_min::min)>> : std::integral_constant::min), customize::enum_range::min> {}; - constexpr operator string_view() const noexcept { return {data(), size()}; } +template +struct range_max : std::integral_constant {}; - private: - template - constexpr static_string(string_view str, std::index_sequence) noexcept : chars{{str[I]..., '\0'}} {} +template +struct range_max::max)>> : std::integral_constant::max), customize::enum_range::max> {}; - const std::array chars; +struct str_view { + const char* str_ = nullptr; + std::size_t size_ = 0; }; -template <> -struct static_string<0> { - constexpr explicit static_string(string_view) noexcept {} +template +class static_str { +public: + constexpr explicit static_str(str_view str) noexcept : static_str{str.str_, std::make_integer_sequence{}} { + MAGIC_ENUM_ASSERT(str.size_ == N); + } + + constexpr explicit static_str(string_view str) noexcept : static_str{str.data(), std::make_integer_sequence{}} { + MAGIC_ENUM_ASSERT(str.size() == N); + } - constexpr const char* data() const noexcept { return nullptr; } + constexpr const char_type* data() const noexcept { return chars_; } - constexpr std::size_t size() const noexcept { return 0; } + constexpr std::uint16_t size() const noexcept { return N; } - constexpr operator string_view() const noexcept { return {}; } + constexpr operator string_view() const noexcept { return {data(), size()}; } + +private: + template + constexpr static_str(const char* str, std::integer_sequence) noexcept : chars_{static_cast(str[I])..., static_cast('\0')} {} + + template + constexpr static_str(string_view str, std::integer_sequence) noexcept : chars_{str[I]..., static_cast('\0')} {} + + char_type chars_[static_cast(N) + 1]; }; -struct char_equal_to { - constexpr bool operator()(char lhs, char rhs) const noexcept { - return lhs == rhs; - } +template <> +class static_str<0> { +public: + constexpr explicit static_str() = default; + + constexpr explicit static_str(str_view) noexcept {} + + constexpr explicit static_str(string_view) noexcept {} + + constexpr const char_type* data() const noexcept { return nullptr; } + + constexpr std::uint16_t size() const noexcept { return 0; } + + constexpr operator string_view() const noexcept { return {}; } }; -constexpr string_view pretty_name(string_view name) noexcept { - for (std::size_t i = name.size(); i > 0; --i) { - if (!((name[i - 1] >= '0' && name[i - 1] <= '9') || - (name[i - 1] >= 'a' && name[i - 1] <= 'z') || - (name[i - 1] >= 'A' && name[i - 1] <= 'Z') || - (name[i - 1] == '_'))) { - name.remove_prefix(i); - break; +template > +class case_insensitive { + static constexpr char_type to_lower(char_type c) noexcept { + return (c >= static_cast('A') && c <= static_cast('Z')) ? static_cast(c + (static_cast('a') - static_cast('A'))) : c; } - } - - if (name.size() > 0 && ((name.front() >= 'a' && name.front() <= 'z') || - (name.front() >= 'A' && name.front() <= 'Z') || - (name.front() == '_'))) { - return name; - } - return {}; // Invalid name. -} +public: + template + constexpr auto operator()(L lhs, R rhs) const noexcept -> std::enable_if_t, char_type> && std::is_same_v, char_type>, bool> { + return Op{}(to_lower(lhs), to_lower(rhs)); + } +}; -constexpr std::size_t find(string_view str, char c) noexcept { -#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 -// https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc -// https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html - constexpr auto workaroung = true; +constexpr std::size_t find(string_view str, char_type c) noexcept { +#if defined(__clang__) && __clang_major__ < 9 && defined(__GLIBCXX__) || defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) + // https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc + // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + constexpr bool workaround = true; #else - constexpr auto workaroung = false; + constexpr bool workaround = false; #endif - if constexpr (workaroung) { - for (std::size_t i = 0; i < str.size(); ++i) { - if (str[i] == c) { - return i; - } + + if constexpr (workaround) { + for (std::size_t i = 0; i < str.size(); ++i) { + if (str[i] == c) { + return i; + } + } + + return string_view::npos; + } else { + return str.find(c); } +} + +template +constexpr bool is_default_predicate() noexcept { + return std::is_same_v, std::equal_to> || + std::is_same_v, std::equal_to<>>; +} - return string_view::npos; - } else { - return str.find_first_of(c); - } +template +constexpr bool is_nothrow_invocable() { + return is_default_predicate() || + std::is_nothrow_invocable_r_v; } template -constexpr bool cmp_equal(string_view lhs, string_view rhs, BinaryPredicate&& p) noexcept(std::is_nothrow_invocable_r_v) { -#if defined(_MSC_VER) && _MSC_VER < 1920 - // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html - // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html - constexpr auto workaroung = true; +constexpr bool cmp_equal(string_view lhs, string_view rhs, [[maybe_unused]] BinaryPredicate&& p) noexcept(is_nothrow_invocable()) { +#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) + // https://developercommunity.visualstudio.com/content/problem/360432/vs20178-regression-c-failed-in-test.html + // https://developercommunity.visualstudio.com/content/problem/232218/c-constexpr-string-view.html + constexpr bool workaround = true; #else - constexpr auto workaroung = false; + constexpr bool workaround = false; #endif - if constexpr (std::is_same_v, char_equal_to> && !workaroung) { - static_cast(p); - return lhs == rhs; - } else { - if (lhs.size() != rhs.size()) { - return false; - } - const auto size = lhs.size(); - for (std::size_t i = 0; i < size; ++i) { - if (!p(lhs[i], rhs[i])) { - return false; - } - } + if constexpr (!is_default_predicate() || workaround) { + if (lhs.size() != rhs.size()) { + return false; + } - return true; - } + const auto size = lhs.size(); + for (std::size_t i = 0; i < size; ++i) { + if (!p(lhs[i], rhs[i])) { + return false; + } + } + + return true; + } else { + return lhs == rhs; + } } template constexpr bool cmp_less(L lhs, R rhs) noexcept { - static_assert(std::is_integral_v && std::is_integral_v, "magic_enum::detail::cmp_less requires integral type."); - - if constexpr (std::is_signed_v == std::is_signed_v) { - // If same signedness (both signed or both unsigned). - return lhs < rhs; - } else if constexpr (std::is_signed_v) { - // If 'right' is negative, then result is 'false', otherwise cast & compare. - return rhs > 0 && lhs < static_cast>(rhs); - } else { - // If 'left' is negative, then result is 'true', otherwise cast & compare. - return lhs < 0 || static_cast>(lhs) < rhs; - } + static_assert(std::is_integral_v && std::is_integral_v, "magic_enum::detail::cmp_less requires integral type."); + + if constexpr (std::is_signed_v == std::is_signed_v) { + // If same signedness (both signed or both unsigned). + return lhs < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return static_cast(lhs) < rhs; + } else if constexpr (std::is_same_v) { // bool special case + return lhs < static_cast(rhs); + } else if constexpr (std::is_signed_v) { + // If 'right' is negative, then result is 'false', otherwise cast & compare. + return rhs > 0 && lhs < static_cast>(rhs); + } else { + // If 'left' is negative, then result is 'true', otherwise cast & compare. + return lhs < 0 || static_cast>(lhs) < rhs; + } } template constexpr I log2(I value) noexcept { - static_assert(std::is_integral_v, "magic_enum::detail::log2 requires integral type."); + static_assert(std::is_integral_v, "magic_enum::detail::log2 requires integral type."); - auto ret = I{0}; - for (; value > I{1}; value >>= I{1}, ++ret) {} + if constexpr (std::is_same_v) { // bool special case + return MAGIC_ENUM_ASSERT(false), value; + } else { + auto ret = I{0}; + for (; value > I{1}; value >>= I{1}, ++ret) {} - return ret; + return ret; + } } -template -constexpr bool is_pow2(I x) noexcept { - static_assert(std::is_integral_v, "magic_enum::detail::is_pow2 requires integral type."); - - return x != 0 && (x & (x - 1)) == 0; +#if defined(__cpp_lib_array_constexpr) && __cpp_lib_array_constexpr >= 201603L +# define MAGIC_ENUM_ARRAY_CONSTEXPR 1 +#else +template +constexpr std::array, N> to_array(T (&a)[N], std::index_sequence) noexcept { + return {{a[I]...}}; } +#endif template inline constexpr bool is_enum_v = std::is_enum_v && std::is_same_v>; template constexpr auto n() noexcept { - static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); -#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED -# if defined(__clang__) - constexpr string_view name{__PRETTY_FUNCTION__ + 34, sizeof(__PRETTY_FUNCTION__) - 36}; -# elif defined(__GNUC__) - constexpr string_view name{__PRETTY_FUNCTION__ + 49, sizeof(__PRETTY_FUNCTION__) - 51}; -# elif defined(_MSC_VER) - constexpr string_view name{__FUNCSIG__ + 40, sizeof(__FUNCSIG__) - 57}; -# endif - return static_string{name}; + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + if constexpr (supported::value) { +#if defined(MAGIC_ENUM_GET_TYPE_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_TYPE_NAME_BUILTIN(E); + constexpr auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +#elif defined(__clang__) + str_view name; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else { + name.size_ = sizeof(__PRETTY_FUNCTION__) - 36; + name.str_ = __PRETTY_FUNCTION__ + 34; + } +#elif defined(__GNUC__) + auto name = str_view{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else if (name.str_[name.size_ - 1] == ']') { + name.size_ -= 50; + name.str_ += 49; + } else { + name.size_ -= 40; + name.str_ += 37; + } +#elif defined(_MSC_VER) + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + str_view name; + name.str_ = __FUNCSIG__; + name.str_ += 40; + name.size_ += sizeof(__FUNCSIG__) - 57; #else - return string_view{}; // Unsupported compiler. + auto name = str_view{}; #endif + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + return name; + } else { + return str_view{}; // Unsupported compiler or Invalid customize. + } } template -inline constexpr auto type_name_v = n(); +constexpr auto type_name() noexcept { + [[maybe_unused]] constexpr auto custom = customize::enum_type_name(); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_str{name}; + } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { + return static_str<0>{}; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { + constexpr auto name = n(); + return static_str{name}; + } else { + static_assert(always_false_v, "magic_enum::customize invalid."); + } +} + +template +inline constexpr auto type_name_v = type_name(); + +template +constexpr auto n() noexcept { + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + + if constexpr (supported::value) { +#if defined(MAGIC_ENUM_GET_ENUM_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V); + auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +#elif defined(__clang__) + str_view name; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else { + name.size_ = sizeof(__PRETTY_FUNCTION__) - 36; + name.str_ = __PRETTY_FUNCTION__ + 34; + } + if (name.size_ > 22 && name.str_[0] == '(' && name.str_[1] == 'a' && name.str_[10] == ' ' && name.str_[22] == ':') { + name.size_ -= 23; + name.str_ += 23; + } + if (name.str_[0] == '(' || name.str_[0] == '-' || (name.str_[0] >= '0' && name.str_[0] <= '9')) { + name = str_view{}; + } +#elif defined(__GNUC__) + auto name = str_view{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}; + if constexpr (sizeof(__PRETTY_FUNCTION__) == sizeof(__FUNCTION__)) { + static_assert(always_false_v, "magic_enum::detail::n requires __PRETTY_FUNCTION__."); + return str_view{}; + } else if (name.str_[name.size_ - 1] == ']') { + name.size_ -= 55; + name.str_ += 54; + } else { + name.size_ -= 40; + name.str_ += 37; + } + if (name.str_[0] == '(') { + name = str_view{}; + } +#elif defined(_MSC_VER) + str_view name; + if ((__FUNCSIG__[5] == '_' && __FUNCSIG__[35] != '(') || (__FUNCSIG__[5] == 'c' && __FUNCSIG__[41] != '(')) { + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + name.str_ = __FUNCSIG__; + name.str_ += 35; + name.size_ = sizeof(__FUNCSIG__) - 52; + } +#else + auto name = str_view{}; +#endif + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + return name; + } else { + return str_view{}; // Unsupported compiler or Invalid customize. + } +} +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920 +# define MAGIC_ENUM_VS_2017_WORKAROUND 1 +#endif + +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) template constexpr auto n() noexcept { - static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); - constexpr auto custom_name = customize::enum_name(V); - - if constexpr (custom_name.empty()) { - static_cast(custom_name); -#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED -# if defined(__clang__) || defined(__GNUC__) - constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2}); -# elif defined(_MSC_VER) - constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17}); + static_assert(is_enum_v, "magic_enum::detail::n requires enum type."); + +# if defined(MAGIC_ENUM_GET_ENUM_NAME_BUILTIN) + constexpr auto name_ptr = MAGIC_ENUM_GET_ENUM_NAME_BUILTIN(V); + auto name = name_ptr ? str_view{name_ptr, std::char_traits::length(name_ptr)} : str_view{}; +# else + // CLI/C++ workaround (see https://github.com/Neargye/magic_enum/issues/284). + str_view name; + name.str_ = __FUNCSIG__; + name.size_ = sizeof(__FUNCSIG__) - 17; + std::size_t p = 0; + for (std::size_t i = name.size_; i > 0; --i) { + if (name.str_[i] == ',' || name.str_[i] == ':') { + p = i + 1; + break; + } + } + if (p > 0) { + name.size_ -= p; + name.str_ += p; + } + if (name.str_[0] == '(' || name.str_[0] == '-' || (name.str_[0] >= '0' && name.str_[0] <= '9')) { + name = str_view{}; + } + return name; # endif - return static_string{name}; +} +#endif + +template +constexpr auto enum_name() noexcept { + [[maybe_unused]] constexpr auto custom = customize::enum_name(V); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return static_str{name}; + } else if constexpr (custom.first == customize::detail::customize_tag::invalid_tag) { + return static_str<0>{}; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) + constexpr auto name = n(); #else - return string_view{}; // Unsupported compiler. + constexpr auto name = n(); #endif - } else { - return static_string{custom_name}; - } + return static_str{name}; + } else { + static_assert(always_false_v, "magic_enum::customize invalid."); + } } template -inline constexpr auto enum_name_v = n(); +inline constexpr auto enum_name_v = enum_name(); template constexpr bool is_valid() noexcept { - static_assert(is_enum_v, "magic_enum::detail::is_valid requires enum type."); - - return n(V)>().size() != 0; -} - -template > -constexpr int reflected_min() noexcept { - static_assert(is_enum_v, "magic_enum::detail::reflected_min requires enum type."); - - if constexpr (IsFlags) { - return 0; - } else { - constexpr auto lhs = customize::enum_range::min; - static_assert(lhs > (std::numeric_limits::min)(), "magic_enum::enum_range requires min must be greater than INT16_MIN."); - constexpr auto rhs = (std::numeric_limits::min)(); - - if constexpr (cmp_less(lhs, rhs)) { - return rhs; +#if defined(__clang__) && __clang_major__ >= 16 + // https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 + constexpr E v = __builtin_bit_cast(E, V); +#else + constexpr E v = static_cast(V); +#endif + [[maybe_unused]] constexpr auto custom = customize::enum_name(v); + static_assert(std::is_same_v, customize::customize_t>, "magic_enum::customize requires customize_t type."); + if constexpr (custom.first == customize::detail::customize_tag::custom_tag) { + constexpr auto name = custom.second; + static_assert(!name.empty(), "magic_enum::customize requires not empty string."); + return name.size() != 0; + } else if constexpr (custom.first == customize::detail::customize_tag::default_tag) { +#if defined(MAGIC_ENUM_VS_2017_WORKAROUND) + return n().size_ != 0; +#else + return n().size_ != 0; +#endif } else { - return lhs; + return false; } - } } -template > -constexpr int reflected_max() noexcept { - static_assert(is_enum_v, "magic_enum::detail::reflected_max requires enum type."); +enum class enum_subtype { + common, + flags +}; - if constexpr (IsFlags) { - return std::numeric_limits::digits - 1; - } else { - constexpr auto lhs = customize::enum_range::max; - static_assert(lhs < (std::numeric_limits::max)(), "magic_enum::enum_range requires max must be less than INT16_MAX."); - constexpr auto rhs = (std::numeric_limits::max)(); +template > +constexpr U ualue(std::size_t i) noexcept { + if constexpr (std::is_same_v) { // bool special case + static_assert(O == 0, "magic_enum::detail::ualue requires valid offset."); - if constexpr (cmp_less(lhs, rhs)) { - return lhs; + return static_cast(i); + } else if constexpr (S == enum_subtype::flags) { + return static_cast(U{1} << static_cast(static_cast(i) + O)); } else { - return rhs; + return static_cast(static_cast(i) + O); } - } } -template -inline constexpr auto reflected_min_v = reflected_min(); - -template -inline constexpr auto reflected_max_v = reflected_max(); - -template > +template > constexpr E value(std::size_t i) noexcept { - static_assert(is_enum_v, "magic_enum::detail::value requires enum type."); + return static_cast(ualue(i)); +} - if constexpr (IsFlags) { - return static_cast(U{1} << static_cast(static_cast(i) + O)); - } else { - return static_cast(static_cast(i) + O); - } +template > +constexpr int reflected_min() noexcept { + if constexpr (S == enum_subtype::flags) { + return 0; + } else { + constexpr auto lhs = range_min::value; + constexpr auto rhs = (std::numeric_limits::min)(); + + if constexpr (cmp_less(rhs, lhs)) { + return lhs; + } else { + return rhs; + } + } } -template -constexpr auto values(std::index_sequence) noexcept { - static_assert(is_enum_v, "magic_enum::detail::values requires enum type."); - constexpr std::array valid{{is_valid(I)>()...}}; - constexpr std::size_t count = [](decltype((valid)) valid_) constexpr noexcept -> std::size_t { - auto count_ = std::size_t{0}; - for (std::size_t i_ = 0; i_ < valid_.size(); ++i_) { - if (valid_[i_]) { - ++count_; - } +template > +constexpr int reflected_max() noexcept { + if constexpr (S == enum_subtype::flags) { + return std::numeric_limits::digits - 1; + } else { + constexpr auto lhs = range_max::value; + constexpr auto rhs = (std::numeric_limits::max)(); + + if constexpr (cmp_less(lhs, rhs)) { + return lhs; + } else { + return rhs; + } } - return count_; - }(valid); +} - std::array values{}; - for (std::size_t i = 0, v = 0; v < count; ++i) { - if (valid[i]) { - values[v++] = value(i); +#define MAGIC_ENUM_FOR_EACH_256(T) \ +T( 0)T( 1)T( 2)T( 3)T( 4)T( 5)T( 6)T( 7)T( 8)T( 9)T( 10)T( 11)T( 12)T( 13)T( 14)T( 15)T( 16)T( 17)T( 18)T( 19)T( 20)T( 21)T( 22)T( 23)T( 24)T( 25)T( 26)T( 27)T( 28)T( 29)T( 30)T( 31) \ + T( 32)T( 33)T( 34)T( 35)T( 36)T( 37)T( 38)T( 39)T( 40)T( 41)T( 42)T( 43)T( 44)T( 45)T( 46)T( 47)T( 48)T( 49)T( 50)T( 51)T( 52)T( 53)T( 54)T( 55)T( 56)T( 57)T( 58)T( 59)T( 60)T( 61)T( 62)T( 63) \ + T( 64)T( 65)T( 66)T( 67)T( 68)T( 69)T( 70)T( 71)T( 72)T( 73)T( 74)T( 75)T( 76)T( 77)T( 78)T( 79)T( 80)T( 81)T( 82)T( 83)T( 84)T( 85)T( 86)T( 87)T( 88)T( 89)T( 90)T( 91)T( 92)T( 93)T( 94)T( 95) \ + T( 96)T( 97)T( 98)T( 99)T(100)T(101)T(102)T(103)T(104)T(105)T(106)T(107)T(108)T(109)T(110)T(111)T(112)T(113)T(114)T(115)T(116)T(117)T(118)T(119)T(120)T(121)T(122)T(123)T(124)T(125)T(126)T(127) \ + T(128)T(129)T(130)T(131)T(132)T(133)T(134)T(135)T(136)T(137)T(138)T(139)T(140)T(141)T(142)T(143)T(144)T(145)T(146)T(147)T(148)T(149)T(150)T(151)T(152)T(153)T(154)T(155)T(156)T(157)T(158)T(159) \ + T(160)T(161)T(162)T(163)T(164)T(165)T(166)T(167)T(168)T(169)T(170)T(171)T(172)T(173)T(174)T(175)T(176)T(177)T(178)T(179)T(180)T(181)T(182)T(183)T(184)T(185)T(186)T(187)T(188)T(189)T(190)T(191) \ + T(192)T(193)T(194)T(195)T(196)T(197)T(198)T(199)T(200)T(201)T(202)T(203)T(204)T(205)T(206)T(207)T(208)T(209)T(210)T(211)T(212)T(213)T(214)T(215)T(216)T(217)T(218)T(219)T(220)T(221)T(222)T(223) \ + T(224)T(225)T(226)T(227)T(228)T(229)T(230)T(231)T(232)T(233)T(234)T(235)T(236)T(237)T(238)T(239)T(240)T(241)T(242)T(243)T(244)T(245)T(246)T(247)T(248)T(249)T(250)T(251)T(252)T(253)T(254)T(255) + + template + constexpr void valid_count(bool* valid, std::size_t& count) noexcept { +#define MAGIC_ENUM_V(O) \ + if constexpr ((I + O) < Size) { \ + if constexpr (is_valid(I + O)>()) { \ + valid[I + O] = true; \ + ++count; \ + } \ } - } - return values; + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_V) + + if constexpr ((I + 256) < Size) { + valid_count(valid, count); + } +#undef MAGIC_ENUM_V } -template > -constexpr auto values() noexcept { - static_assert(is_enum_v, "magic_enum::detail::values requires enum type."); - constexpr auto range_size = reflected_max_v - reflected_min_v + 1; - static_assert(range_size > 0, "magic_enum::enum_range requires valid size."); - static_assert(range_size < (std::numeric_limits::max)(), "magic_enum::enum_range requires valid size."); +template +struct valid_count_t { + std::size_t count = 0; + bool valid[N] = {}; +}; - return values>(std::make_index_sequence{}); +template +constexpr auto valid_count() noexcept { + valid_count_t vc; + valid_count(vc.valid, vc.count); + return vc; } -template -inline constexpr auto values_v = values(); - -template > -using values_t = decltype((values_v)); +template +constexpr auto values() noexcept { + constexpr auto vc = valid_count(); -template -inline constexpr auto count_v = values_v.size(); + if constexpr (vc.count > 0) { +#if defined(MAGIC_ENUM_ARRAY_CONSTEXPR) + std::array values = {}; +#else + E values[vc.count] = {}; +#endif + for (std::size_t i = 0, v = 0; v < vc.count; ++i) { + if (vc.valid[i]) { + values[v++] = value(i); + } + } +#if defined(MAGIC_ENUM_ARRAY_CONSTEXPR) + return values; +#else + return to_array(values, std::make_index_sequence{}); +#endif + } else { + return std::array{}; + } +} -template > -inline constexpr auto min_v = static_cast(values_v.front()); +template > +constexpr auto values() noexcept { + constexpr auto min = reflected_min(); + constexpr auto max = reflected_max(); + constexpr auto range_size = max - min + 1; + static_assert(range_size > 0, "magic_enum::enum_range requires valid size."); -template > -inline constexpr auto max_v = static_cast(values_v.back()); + return values(); +} -template > -constexpr std::size_t range_size() noexcept { - static_assert(is_enum_v, "magic_enum::detail::range_size requires enum type."); - constexpr auto max = IsFlags ? log2(max_v) : max_v; - constexpr auto min = IsFlags ? log2(min_v) : min_v; - constexpr auto range_size = max - min + U{1}; - static_assert(range_size > 0, "magic_enum::enum_range requires valid size."); - static_assert(range_size < (std::numeric_limits::max)(), "magic_enum::enum_range requires valid size."); +template > +constexpr enum_subtype subtype(std::true_type) noexcept { + if constexpr (std::is_same_v) { // bool special case + return enum_subtype::common; + } else if constexpr (has_is_flags::value) { + return customize::enum_range::is_flags ? enum_subtype::flags : enum_subtype::common; + } else { +#if defined(MAGIC_ENUM_AUTO_IS_FLAGS) + constexpr auto flags_values = values(); + constexpr auto default_values = values(); + if (flags_values.size() == 0 || default_values.size() > flags_values.size()) { + return enum_subtype::common; + } + for (std::size_t i = 0; i < default_values.size(); ++i) { + const auto v = static_cast(default_values[i]); + if (v != 0 && (v & (v - 1)) != 0) { + return enum_subtype::common; + } + } + return enum_subtype::flags; +#else + return enum_subtype::common; +#endif + } +} - return static_cast(range_size); +template +constexpr enum_subtype subtype(std::false_type) noexcept { + // For non-enum type return default common subtype. + return enum_subtype::common; } -template -inline constexpr auto range_size_v = range_size(); +template > +inline constexpr auto subtype_v = subtype(std::is_enum{}); -template -using index_t = std::conditional_t < (std::numeric_limits::max)(), std::uint8_t, std::uint16_t>; +template +inline constexpr auto values_v = values(); -template -inline constexpr auto invalid_index_v = (std::numeric_limits>::max)(); +template > +using values_t = decltype((values_v)); -template -constexpr auto indexes(std::index_sequence) noexcept { - static_assert(is_enum_v, "magic_enum::detail::indexes requires enum type."); - constexpr auto min = IsFlags ? log2(min_v) : min_v; - [[maybe_unused]] auto i = index_t{0}; +template +inline constexpr auto count_v = values_v.size(); - return std::array{{(is_valid(I)>() ? i++ : invalid_index_v)...}}; -} +template > +inline constexpr auto min_v = (count_v > 0) ? static_cast(values_v.front()) : U{0}; -template -inline constexpr auto indexes_v = indexes(std::make_index_sequence>{}); +template > +inline constexpr auto max_v = (count_v > 0) ? static_cast(values_v.back()) : U{0}; -template +template constexpr auto names(std::index_sequence) noexcept { - static_assert(is_enum_v, "magic_enum::detail::names requires enum type."); - - return std::array{{enum_name_v[I]>...}}; + constexpr auto names = std::array{{enum_name_v[I]>...}}; + return names; } -template -inline constexpr auto names_v = names(std::make_index_sequence>{}); +template +inline constexpr auto names_v = names(std::make_index_sequence>{}); -template > -using names_t = decltype((names_v)); +template > +using names_t = decltype((names_v)); -template +template constexpr auto entries(std::index_sequence) noexcept { - static_assert(is_enum_v, "magic_enum::detail::entries requires enum type."); - - return std::array, sizeof...(I)>{{{values_v[I], enum_name_v[I]>}...}}; + constexpr auto entries = std::array, sizeof...(I)>{{{values_v[I], enum_name_v[I]>}...}}; + return entries; } -template -inline constexpr auto entries_v = entries(std::make_index_sequence>{}); +template +inline constexpr auto entries_v = entries(std::make_index_sequence>{}); -template > -using entries_t = decltype((entries_v)); +template > +using entries_t = decltype((entries_v)); -template > +template > constexpr bool is_sparse() noexcept { - static_assert(is_enum_v, "magic_enum::detail::is_sparse requires enum type."); - - return range_size_v != count_v; -} - -template -inline constexpr bool is_sparse_v = is_sparse(); - -template > -constexpr std::size_t undex(U value) noexcept { - static_assert(is_enum_v, "magic_enum::detail::undex requires enum type."); - - if (const auto i = static_cast(value - min_v); value >= min_v && value <= max_v) { - if constexpr (is_sparse_v) { - if (const auto idx = indexes_v[i]; idx != invalid_index_v) { - return idx; - } + if constexpr (count_v == 0) { + return false; + } else if constexpr (std::is_same_v) { // bool special case + return false; } else { - return i; - } - } - - return invalid_index_v; // Value out of range. -} - -template > -constexpr std::size_t endex(E value) noexcept { - static_assert(is_enum_v, "magic_enum::detail::endex requires enum type."); + constexpr auto max = (S == enum_subtype::flags) ? log2(max_v) : max_v; + constexpr auto min = (S == enum_subtype::flags) ? log2(min_v) : min_v; + constexpr auto range_size = max - min + 1; - return undex(static_cast(value)); + return range_size != count_v; + } } -template > -constexpr U value_ors() noexcept { - static_assert(is_enum_v, "magic_enum::detail::endex requires enum type."); +template > +inline constexpr bool is_sparse_v = is_sparse(); - auto value = U{0}; - for (std::size_t i = 0; i < count_v; ++i) { - value |= static_cast(values_v[i]); - } +template +struct is_reflected +#if defined(MAGIC_ENUM_NO_CHECK_REFLECTED_ENUM) + : std::true_type {}; +#else + : std::bool_constant && (count_v != 0)> {}; +#endif - return value; -} +template +inline constexpr bool is_reflected_v = is_reflected, S>{}; -template +template struct enable_if_enum {}; -template -struct enable_if_enum { - using type = R; - using D = std::decay_t; - static_assert(supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); +template +struct enable_if_enum { + using type = R; + static_assert(supported::value, "magic_enum unsupported compiler (https://github.com/Neargye/magic_enum#compiler-compatibility)."); }; -template -using enable_if_enum_t = std::enable_if_t>, R>; +template , typename D = std::decay_t> +using enable_if_t = typename enable_if_enum && std::is_invocable_r_v, R>::type; -template >>> +template >, int> = 0> using enum_concept = T; template > @@ -590,6 +909,217 @@ struct underlying_type {}; template struct underlying_type : std::underlying_type> {}; +#if defined(MAGIC_ENUM_ENABLE_HASH) || defined(MAGIC_ENUM_ENABLE_HASH_SWITCH) + +template +struct constexpr_hash_t; + +template +struct constexpr_hash_t>> { + constexpr auto operator()(Value value) const noexcept { + using U = typename underlying_type::type; + if constexpr (std::is_same_v) { // bool special case + return static_cast(value); + } else { + return static_cast(value); + } + } + using secondary_hash = constexpr_hash_t; +}; + +template +struct constexpr_hash_t>> { + static constexpr std::uint32_t crc_table[256] { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL + }; + constexpr std::uint32_t operator()(string_view value) const noexcept { + auto crc = static_cast(0xffffffffL); + for (const auto c : value) { + crc = (crc >> 8) ^ crc_table[(crc ^ static_cast(c)) & 0xff]; + } + return crc ^ 0xffffffffL; + } + + struct secondary_hash { + constexpr std::uint32_t operator()(string_view value) const noexcept { + auto acc = static_cast(2166136261ULL); + for (const auto c : value) { + acc = ((acc ^ static_cast(c)) * static_cast(16777619ULL)) & (std::numeric_limits::max)(); + } + return static_cast(acc); + } + }; +}; + +template +inline constexpr Hash hash_v{}; + +template +constexpr auto calculate_cases(std::size_t Page) noexcept { + constexpr std::array values = *GlobValues; + constexpr std::size_t size = values.size(); + + using switch_t = std::invoke_result_t; + static_assert(std::is_integral_v && !std::is_same_v); + const std::size_t values_to = (std::min)(static_cast(256), size - Page); + + std::array result{}; + auto fill = result.begin(); + { + auto first = values.begin() + static_cast(Page); + auto last = values.begin() + static_cast(Page + values_to); + while (first != last) { + *fill++ = hash_v(*first++); + } + } + + // dead cases, try to avoid case collisions + for (switch_t last_value = result[values_to - 1]; fill != result.end() && last_value != (std::numeric_limits::max)(); *fill++ = ++last_value) { + } + + { + auto it = result.begin(); + auto last_value = (std::numeric_limits::min)(); + for (; fill != result.end(); *fill++ = last_value++) { + while (last_value == *it) { + ++last_value, ++it; + } + } + } + + return result; +} + +template +constexpr R invoke_r(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_r_v) { + if constexpr (std::is_void_v) { + std::forward(f)(std::forward(args)...); + } else { + return static_cast(std::forward(f)(std::forward(args)...)); + } +} + +enum class case_call_t { + index, + value +}; + +template +inline constexpr auto default_result_type_lambda = []() noexcept(std::is_nothrow_default_constructible_v) { return T{}; }; + +template <> +inline constexpr auto default_result_type_lambda = []() noexcept {}; + +template +constexpr bool has_duplicate() noexcept { + using value_t = std::decay_t; + using hash_value_t = std::invoke_result_t; + std::arraysize()> hashes{}; + std::size_t size = 0; + for (auto elem : *Arr) { + hashes[size] = hash_v(elem); + for (auto i = size++; i > 0; --i) { + if (hashes[i] < hashes[i - 1]) { + auto tmp = hashes[i]; + hashes[i] = hashes[i - 1]; + hashes[i - 1] = tmp; + } else if (hashes[i] == hashes[i - 1]) { + return false; + } else { + break; + } + } + } + return true; +} + +#define MAGIC_ENUM_CASE(val) \ +case cases[val]: \ + if constexpr ((val) + Page < size) { \ + if (!pred(values[val + Page], searched)) { \ + break; \ + } \ + if constexpr (CallValue == case_call_t::index) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), std::integral_constant{}); \ + } else if constexpr (std::is_invocable_v>) { \ + MAGIC_ENUM_ASSERT(false && "magic_enum::detail::constexpr_switch wrong result type."); \ + } \ + } else if constexpr (CallValue == case_call_t::value) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), enum_constant{}); \ + } else if constexpr (std::is_invocable_r_v>) { \ + MAGIC_ENUM_ASSERT(false && "magic_enum::detail::constexpr_switch wrong result type."); \ + } \ + } \ + break; \ +} else [[fallthrough]]; + +template ::value_type>, + typename BinaryPredicate = std::equal_to<>, + typename Lambda, + typename ResultGetterType> +constexpr decltype(auto) constexpr_switch( + Lambda&& lambda, + typename std::decay_t::value_type searched, + ResultGetterType&& def, + BinaryPredicate&& pred = {}) { + using result_t = std::invoke_result_t; + using hash_t = std::conditional_t(), Hash, typename Hash::secondary_hash>; + static_assert(has_duplicate(), "magic_enum::detail::constexpr_switch duplicated hash found, please report it: https://github.com/Neargye/magic_enum/issues."); + constexpr std::array values = *GlobValues; + constexpr std::size_t size = values.size(); + constexpr std::array cases = calculate_cases(Page); + + switch (hash_v(searched)) { + MAGIC_ENUM_FOR_EACH_256(MAGIC_ENUM_CASE) + default: + if constexpr (size > 256 + Page) { + return constexpr_switch(std::forward(lambda), searched, std::forward(def)); + } + break; + } + return def(); +} + +#undef MAGIC_ENUM_CASE + +#endif + } // namespace magic_enum::detail // Checks is magic_enum supported compiler. @@ -622,477 +1152,308 @@ struct underlying_type : detail::underlying_type {}; template using underlying_type_t = typename underlying_type::type; +template +using enum_constant = detail::enum_constant; + // Returns type name of enum. template -[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - constexpr string_view name = detail::type_name_v; - static_assert(name.size() > 0, "Enum type does not have a name."); +[[nodiscard]] constexpr auto enum_type_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::type_name_v>; + static_assert(!name.empty(), "magic_enum::enum_type_name enum type does not have a name."); - return name; + return name; } // Returns number of enum values. -template -[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - - return detail::count_v; +template > +[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_t { + return detail::count_v, S>; } // Returns enum value at specified index. // No bounds checking is performed: the behavior is undefined if index >= number of enum values. -template -[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum requires enum implementation and valid max and min."); - - if constexpr (detail::is_sparse_v) { - return assert((index < detail::count_v)), detail::values_v[index]; - } else { - return assert((index < detail::count_v)), detail::value>(index); - } -} +template > +[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); -// Returns std::array with enum values, sorted by enum value. -template -[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum requires enum implementation and valid max and min."); - - return detail::values_v; -} - -// Returns name from static storage enum variable. -// This version is much lighter on the compile times and is not restricted to the enum_range limitation. -template -[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - constexpr string_view name = detail::enum_name_v; - static_assert(name.size() > 0, "Enum value does not have a name."); + if constexpr (detail::is_sparse_v) { + return MAGIC_ENUM_ASSERT(index < detail::count_v), detail::values_v[index]; + } else { + constexpr auto min = (S == detail::enum_subtype::flags) ? detail::log2(detail::min_v) : detail::min_v; - return name; + return MAGIC_ENUM_ASSERT(index < detail::count_v), detail::value(index); + } } -// Returns name from enum value. -// If enum value does not have name or value out of range, returns empty string. -template -[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - - if (const auto i = detail::endex(value); i != detail::invalid_index_v) { - return detail::names_v[i]; - } +// Returns enum value at specified index. +template > +[[nodiscard]] constexpr auto enum_value() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + static_assert(I < detail::count_v, "magic_enum::enum_value out of range."); - return {}; // Invalid value or out of range. + return enum_value(I); } -// Returns std::array with names, sorted by enum value. -template -[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum requires enum implementation and valid max and min."); +// Returns std::array with enum values, sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); - return detail::names_v; + return detail::values_v; } -// Returns std::array with pairs (value, name), sorted by enum value. +// Returns integer value from enum value. template -[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum requires enum implementation and valid max and min."); - - return detail::entries_v; +[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_t> { + return static_cast>(value); } -// Obtains enum value from integer value. -// Returns optional with enum value. +// Returns underlying value from enum value. template -[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_enum_t>> { - using D = std::decay_t; - - if (detail::undex(value) != detail::invalid_index_v) { - return static_cast(value); - } - - return {}; // Invalid value or out of range. +[[nodiscard]] constexpr auto enum_underlying(E value) noexcept -> detail::enable_if_t> { + return static_cast>(value); } -// Obtains enum value from name. -// Returns optional with enum value. -template -[[nodiscard]] constexpr auto enum_cast(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v) -> detail::enable_if_enum_t>> { - static_assert(std::is_invocable_r_v, "magic_enum::enum_cast requires bool(char, char) invocable predicate."); - using D = std::decay_t; - - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (detail::cmp_equal(value, detail::names_v[i], p)) { - return enum_value(i); +// Obtains index in enum values from enum value. +// Returns optional with index. +template > +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + using U = underlying_type_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v || (S == detail::enum_subtype::flags)) { +#if defined(MAGIC_ENUM_ENABLE_HASH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::index>( + [](std::size_t i) { return optional{i}; }, + value, + detail::default_result_type_lambda>); +#else + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (enum_value(i) == value) { + return i; + } + } + return {}; // Invalid value or out of range. +#endif + } else { + const auto v = static_cast(value); + if (v >= detail::min_v && v <= detail::max_v) { + return static_cast(v - detail::min_v); + } + return {}; // Invalid value or out of range. } - } - - return {}; // Invalid value or out of range. -} - -// Obtains enum value from name. -// Returns optional with enum value. -template -[[nodiscard]] constexpr auto enum_cast(string_view value) noexcept -> detail::enable_if_enum_t>> { - using D = std::decay_t; - - return enum_cast(value, detail::char_equal_to{}); -} - -// Returns integer value from enum value. -template -[[nodiscard]] constexpr auto enum_integer(E value) noexcept -> detail::enable_if_enum_t> { - return static_cast>(value); } // Obtains index in enum values from enum value. // Returns optional with index. -template -[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - - if (const auto i = detail::endex(value); i != detail::invalid_index_v) { - return i; - } - - return {}; // Invalid value or out of range. -} - -// Checks whether enum contains enumerator with such enum value. -template -[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; +template +[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); - return detail::endex(value) != detail::invalid_index_v; + return enum_index(value); } -// Checks whether enum contains enumerator with such integer value. -template -[[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; +// Obtains index in enum values from static storage enum variable. +template >> +[[nodiscard]] constexpr auto enum_index() noexcept -> detail::enable_if_t {\ + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + constexpr auto index = enum_index(V); + static_assert(index, "magic_enum::enum_index enum value does not have a index."); - return detail::undex(value) != detail::invalid_index_v; + return *index; } -// Checks whether enum contains enumerator with such name. -template -[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v) -> detail::enable_if_enum_t { - using D = std::decay_t; - static_assert(std::is_invocable_r_v, "magic_enum::enum_contains requires bool(char, char) invocable predicate."); - - return enum_cast(value, std::move_if_noexcept(p)).has_value(); -} - -// Checks whether enum contains enumerator with such name. -template -[[nodiscard]] constexpr auto enum_contains(string_view value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; +// Returns name from static storage enum variable. +// This version is much lighter on the compile times and is not restricted to the enum_range limitation. +template +[[nodiscard]] constexpr auto enum_name() noexcept -> detail::enable_if_t { + constexpr string_view name = detail::enum_name_v, V>; + static_assert(!name.empty(), "magic_enum::enum_name enum value does not have a name."); - return enum_cast(value).has_value(); + return name; } -namespace ostream_operators { +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template > +[[nodiscard]] constexpr auto enum_name(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); -template , int> = 0> -std::basic_ostream& operator<<(std::basic_ostream& os, E value) { - using D = std::decay_t; - using U = underlying_type_t; -#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED - if (const auto name = magic_enum::enum_name(value); !name.empty()) { - for (const auto c : name) { - os.put(c); + if (const auto i = enum_index(value)) { + return detail::names_v[*i]; } - return os; - } -#endif - return (os << static_cast(value)); + return {}; } -template , int> = 0> -std::basic_ostream& operator<<(std::basic_ostream& os, optional value) { - return value.has_value() ? (os << value.value()) : os; -} - -} // namespace magic_enum::ostream_operators - -namespace bitwise_operators { - -template , int> = 0> -constexpr E operator~(E rhs) noexcept { - return static_cast(~static_cast>(rhs)); -} - -template , int> = 0> -constexpr E operator|(E lhs, E rhs) noexcept { - return static_cast(static_cast>(lhs) | static_cast>(rhs)); -} - -template , int> = 0> -constexpr E operator&(E lhs, E rhs) noexcept { - return static_cast(static_cast>(lhs) & static_cast>(rhs)); -} - -template , int> = 0> -constexpr E operator^(E lhs, E rhs) noexcept { - return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); -} - -template , int> = 0> -constexpr E& operator|=(E& lhs, E rhs) noexcept { - return lhs = (lhs | rhs); -} - -template , int> = 0> -constexpr E& operator&=(E& lhs, E rhs) noexcept { - return lhs = (lhs & rhs); -} +// Returns name from enum value. +// If enum value does not have name or value out of range, returns empty string. +template +[[nodiscard]] constexpr auto enum_name(E value) -> detail::enable_if_t { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); -template , int> = 0> -constexpr E& operator^=(E& lhs, E rhs) noexcept { - return lhs = (lhs ^ rhs); + return enum_name(value); } -} // namespace magic_enum::bitwise_operators - -namespace flags { - -// Returns type name of enum. -using magic_enum::enum_type_name; - -// Returns number of enum-flags values. -template -[[nodiscard]] constexpr auto enum_count() noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; +// Returns std::array with names, sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); - return detail::count_v; + return detail::names_v; } -// Returns enum-flags value at specified index. -// No bounds checking is performed: the behavior is undefined if index >= number of enum-flags values. -template -[[nodiscard]] constexpr auto enum_value(std::size_t index) noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum::flags requires enum-flags implementation."); - - if constexpr (detail::is_sparse_v) { - return assert((index < detail::count_v)), detail::values_v[index]; - } else { - constexpr auto min = detail::log2(detail::min_v); +// Returns std::array with pairs (value, name), sorted by enum value. +template > +[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_t> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); - return assert((index < detail::count_v)), detail::value(index); - } + return detail::entries_v; } -// Returns std::array with enum-flags values, sorted by enum-flags value. -template -[[nodiscard]] constexpr auto enum_values() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum::flags requires enum-flags implementation."); +// Allows you to write magic_enum::enum_cast("bar", magic_enum::case_insensitive); +inline constexpr auto case_insensitive = detail::case_insensitive<>{}; - return detail::values_v; +// Obtains enum value from integer value. +// Returns optional with enum value. +template > +[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_t>> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + + if constexpr (detail::is_sparse_v || (S == detail::enum_subtype::flags)) { +#if defined(MAGIC_ENUM_ENABLE_HASH) + return detail::constexpr_switch<&detail::values_v, detail::case_call_t::value>( + [](D v) { return optional{v}; }, + static_cast(value), + detail::default_result_type_lambda>); +#else + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (value == static_cast>(enum_value(i))) { + return static_cast(value); + } + } + return {}; // Invalid value or out of range. +#endif + } else { + if (value >= detail::min_v && value <= detail::max_v) { + return static_cast(value); + } + return {}; // Invalid value or out of range. + } } -// Returns name from enum-flags value. -// If enum-flags value does not have name or value out of range, returns empty string. -template -[[nodiscard]] auto enum_name(E value) -> detail::enable_if_enum_t { - using D = std::decay_t; - using U = underlying_type_t; - - string name; - auto check_value = U{0}; - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (const auto v = static_cast(enum_value(i)); (static_cast(value) & v) != 0) { - check_value |= v; - const auto n = detail::names_v[i]; - if (!name.empty()) { - name.append(1, '|'); - } - name.append(n.data(), n.size()); +// Obtains enum value from name. +// Returns optional with enum value. +template , typename BinaryPredicate = std::equal_to<>> +[[nodiscard]] constexpr auto enum_cast(string_view value, [[maybe_unused]] BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t>, BinaryPredicate> { + using D = std::decay_t; + static_assert(detail::is_reflected_v, "magic_enum requires enum implementation and valid max and min."); + +#if defined(MAGIC_ENUM_ENABLE_HASH) + if constexpr (detail::is_default_predicate()) { + return detail::constexpr_switch<&detail::names_v, detail::case_call_t::index>( + [](std::size_t i) { return optional{detail::values_v[i]}; }, + value, + detail::default_result_type_lambda>, + [&p](string_view lhs, string_view rhs) { return detail::cmp_equal(lhs, rhs, p); }); } - } - - if (check_value != 0 && check_value == static_cast(value)) { - return name; - } - - return {}; // Invalid value or out of range. +#endif + for (std::size_t i = 0; i < detail::count_v; ++i) { + if (detail::cmp_equal(value, detail::names_v[i], p)) { + return enum_value(i); + } + } + return {}; // Invalid value or out of range. } -// Returns std::array with string names, sorted by enum-flags value. -template -[[nodiscard]] constexpr auto enum_names() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum::flags requires enum-flags implementation."); +// Checks whether enum contains value with such value. +template > +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; - return detail::names_v; + return static_cast(enum_cast(static_cast(value))); } -// Returns std::array with pairs (value, name), sorted by enum-flags value. -template -[[nodiscard]] constexpr auto enum_entries() noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - static_assert(detail::count_v > 0, "magic_enum::flags requires enum-flags implementation."); +// Checks whether enum contains value with such value. +template +[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_t { + using D = std::decay_t; + using U = underlying_type_t; - return detail::entries_v; + return static_cast(enum_cast(static_cast(value))); } -// Obtains enum-flags value from integer value. -// Returns optional with enum-flags value. -template -[[nodiscard]] constexpr auto enum_cast(underlying_type_t value) noexcept -> detail::enable_if_enum_t>> { - using D = std::decay_t; - using U = underlying_type_t; - - if constexpr (detail::is_sparse_v) { - auto check_value = U{0}; - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (const auto v = static_cast(enum_value(i)); (value & v) != 0) { - check_value |= v; - } - } - - if (check_value != 0 && check_value == value) { - return static_cast(value); - } - } else { - constexpr auto min = detail::min_v; - constexpr auto max = detail::value_ors(); +// Checks whether enum contains value with such integer value. +template > +[[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_t { + using D = std::decay_t; - if (value >= min && value <= max) { - return static_cast(value); - } - } - - return {}; // Invalid value or out of range. + return static_cast(enum_cast(value)); } -// Obtains enum-flags value from name. -// Returns optional with enum-flags value. -template -[[nodiscard]] constexpr auto enum_cast(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v) -> detail::enable_if_enum_t>> { - static_assert(std::is_invocable_r_v, "magic_enum::flags::enum_cast requires bool(char, char) invocable predicate."); - using D = std::decay_t; - using U = underlying_type_t; - - auto result = U{0}; - while (!value.empty()) { - const auto d = detail::find(value, '|'); - const auto s = (d == string_view::npos) ? value : value.substr(0, d); - auto f = U{0}; - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (detail::cmp_equal(s, detail::names_v[i], p)) { - f = static_cast(enum_value(i)); - result |= f; - break; - } - } - if (f == U{0}) { - return {}; // Invalid value or out of range. - } - value.remove_prefix((d == string_view::npos) ? value.size() : d + 1); - } +// Checks whether enum contains enumerator with such name. +template , typename BinaryPredicate = std::equal_to<>> +[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p = {}) noexcept(detail::is_nothrow_invocable()) -> detail::enable_if_t { + using D = std::decay_t; - if (result == U{0}) { - return {}; // Invalid value or out of range. - } else { - return static_cast(result); - } + return static_cast(enum_cast(value, std::move(p))); } -// Obtains enum-flags value from name. -// Returns optional with enum-flags value. -template -[[nodiscard]] constexpr auto enum_cast(string_view value) noexcept -> detail::enable_if_enum_t>> { - using D = std::decay_t; +template +inline constexpr auto as_flags = AsFlags ? detail::enum_subtype::flags : detail::enum_subtype::common; - return enum_cast(value, detail::char_equal_to{}); -} +template +inline constexpr auto as_common = AsFlags ? detail::enum_subtype::common : detail::enum_subtype::flags; -// Returns integer value from enum value. -using magic_enum::enum_integer; - -// Obtains index in enum-flags values from enum-flags value. -// Returns optional with index. -template -[[nodiscard]] constexpr auto enum_index(E value) noexcept -> detail::enable_if_enum_t> { - using D = std::decay_t; - using U = underlying_type_t; - - if (detail::is_pow2(static_cast(value))) { - for (std::size_t i = 0; i < detail::count_v; ++i) { - if (enum_value(i) == value) { - return i; - } - } - } +namespace bitwise_operators { - return {}; // Invalid value or out of range. +template = 0> +constexpr E operator~(E rhs) noexcept { + return static_cast(~static_cast>(rhs)); } -// Checks whether enum-flags contains enumerator with such enum-flags value. -template -[[nodiscard]] constexpr auto enum_contains(E value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - using U = underlying_type_t; - - return enum_cast(static_cast(value)).has_value(); +template = 0> +constexpr E operator|(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) | static_cast>(rhs)); } -// Checks whether enum-flags contains enumerator with such integer value. -template -[[nodiscard]] constexpr auto enum_contains(underlying_type_t value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - - return enum_cast(value).has_value(); +template = 0> +constexpr E operator&(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) & static_cast>(rhs)); } -// Checks whether enum-flags contains enumerator with such name. -template -[[nodiscard]] constexpr auto enum_contains(string_view value, BinaryPredicate p) noexcept(std::is_nothrow_invocable_r_v) -> detail::enable_if_enum_t { - static_assert(std::is_invocable_r_v, "magic_enum::flags::enum_contains requires bool(char, char) invocable predicate."); - using D = std::decay_t; - - return enum_cast(value, std::move_if_noexcept(p)).has_value(); +template = 0> +constexpr E operator^(E lhs, E rhs) noexcept { + return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); } -// Checks whether enum-flags contains enumerator with such name. -template -[[nodiscard]] constexpr auto enum_contains(string_view value) noexcept -> detail::enable_if_enum_t { - using D = std::decay_t; - - return enum_cast(value).has_value(); +template = 0> +constexpr E& operator|=(E& lhs, E rhs) noexcept { + return lhs = (lhs | rhs); } -} // namespace magic_enum::flags - -namespace flags::ostream_operators { - -template , int> = 0> -std::basic_ostream& operator<<(std::basic_ostream& os, E value) { - using D = std::decay_t; - using U = underlying_type_t; -#if defined(MAGIC_ENUM_SUPPORTED) && MAGIC_ENUM_SUPPORTED - if (const auto name = magic_enum::flags::enum_name(value); !name.empty()) { - for (const auto c : name) { - os.put(c); - } - return os; - } -#endif - return (os << static_cast(value)); +template = 0> +constexpr E& operator&=(E& lhs, E rhs) noexcept { + return lhs = (lhs & rhs); } -template , int> = 0> -std::basic_ostream& operator<<(std::basic_ostream& os, optional value) { - return value.has_value() ? (os << value.value()) : os; +template = 0> +constexpr E& operator^=(E& lhs, E rhs) noexcept { + return lhs = (lhs ^ rhs); } -} // namespace magic_enum::flags::ostream_operators +} // namespace magic_enum::bitwise_operators } // namespace magic_enum @@ -1104,4 +1465,10 @@ std::basic_ostream& operator<<(std::basic_ostream& o # pragma warning(pop) #endif +#undef MAGIC_ENUM_GET_ENUM_NAME_BUILTIN +#undef MAGIC_ENUM_GET_TYPE_NAME_BUILTIN +#undef MAGIC_ENUM_VS_2017_WORKAROUND +#undef MAGIC_ENUM_ARRAY_CONSTEXPR +#undef MAGIC_ENUM_FOR_EACH_256 + #endif // NEARGYE_MAGIC_ENUM_HPP From f6162e4978eefd4f83916e545896ff85be91e93d Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 26 Apr 2024 10:18:32 +0200 Subject: [PATCH 061/101] IO_Image: fix indentation --- src/io_image/io_image.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/io_image/io_image.cpp b/src/io_image/io_image.cpp index a8dc7731..88f09b6a 100644 --- a/src/io_image/io_image.cpp +++ b/src/io_image/io_image.cpp @@ -57,11 +57,13 @@ class ImageWriter::Properties : public PropertyGroup { this->height.setRange(0, std::numeric_limits::max()); this->cameraOrientation.setDescription( - ImageWriterI18N::textIdTr("Camera orientation expressed in Z-up convention as a unit vector")); + ImageWriterI18N::textIdTr("Camera orientation expressed in Z-up convention as a unit vector") + ); this->cameraProjection.mutableEnumeration().changeTrContext(ImageWriterI18N::textIdContext()); } - void restoreDefaults() override { + void restoreDefaults() override + { const Parameters defaults; this->width.setValue(defaults.width); this->height.setValue(defaults.height); From 19d860cf27f53377324b5e3286fad2c86dea2cb7 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 26 Apr 2024 10:19:18 +0200 Subject: [PATCH 062/101] IO_Dxf: fix compilation warnings --- src/io_dxf/io_dxf.cpp | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/io_dxf/io_dxf.cpp b/src/io_dxf/io_dxf.cpp index c911d79e..b602f4d4 100644 --- a/src/io_dxf/io_dxf.cpp +++ b/src/io_dxf/io_dxf.cpp @@ -500,29 +500,19 @@ void DxfReader::Internal::OnReadText(const Dxf_TEXT& text) // See doc https://ezdxf.readthedocs.io/en/stable/tutorials/text.html const DxfHJustification hjust = text.horizontalJustification; Graphic3d_HorizontalTextAlignment hAlign = Graphic3d_HTA_LEFT; - switch (hjust) { - case DxfHJustification::Center: - case DxfHJustification::Middle: + if (hjust == DxfHJustification::Center || hjust == DxfHJustification::Middle) hAlign = Graphic3d_HTA_CENTER; - break; - case DxfHJustification::Right: + else if (hjust == DxfHJustification::Right) hAlign = Graphic3d_HTA_RIGHT; - break; - } const DxfVJustification vjust = text.verticalJustification; Graphic3d_VerticalTextAlignment vAlign = Graphic3d_VTA_TOP; - switch (vjust) { - case DxfVJustification::Baseline: + if (vjust == DxfVJustification::Baseline) vAlign = Graphic3d_VTA_TOPFIRSTLINE; - break; - case DxfVJustification::Bottom: + else if (vjust == DxfVJustification::Bottom) vAlign = Graphic3d_VTA_BOTTOM; - break; - case DxfVJustification::Middle: + else if (vjust == DxfVJustification::Middle) vAlign = Graphic3d_VTA_CENTER; - break; - } // Ensure non-null extrusion direction gp_Vec extDir = toOccVec(text.extrusionDirection); From 15709b517f828778010fa1bc4790c03f3708e3de Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 26 Apr 2024 10:21:37 +0200 Subject: [PATCH 063/101] CI: install Qt with brew on macOS GitHub macos-latest runner seems to have switched to arm64 by default but jurplel/install-qt-action@v3 just provides x86_64 binaries --- .github/workflows/ci_linux_arm.yml | 2 +- .github/workflows/ci_macos.yml | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci_linux_arm.yml b/.github/workflows/ci_linux_arm.yml index d9230349..454044f3 100644 --- a/.github/workflows/ci_linux_arm.yml +++ b/.github/workflows/ci_linux_arm.yml @@ -41,7 +41,7 @@ jobs: apt-get -y install qtbase5-dev libqt5svg5-dev apt-get -y install libxcb-cursor0 # Dependencies for "xcb" Qt plugin(actually needed for Qt6) apt-get -y install libocct-data-exchange-dev libocct-draw-dev occt-misc - apt-get -y install libtbb2-dev libxi-dev # Dependencies for OpenCascade v7.5 + apt-get -y install libtbb-dev libxi-dev # Dependencies for OpenCascade apt-get -y install libassimp-dev ;; fedora*) diff --git a/.github/workflows/ci_macos.yml b/.github/workflows/ci_macos.yml index 7234961a..b74f3c2d 100644 --- a/.github/workflows/ci_macos.yml +++ b/.github/workflows/ci_macos.yml @@ -11,18 +11,13 @@ jobs: uses: actions/checkout@v2 - name: Install Qt - uses: jurplel/install-qt-action@v3 - with: - cache: true - cache-key-prefix: QtCache + run: brew install qt@5 - name: Install OpenCascade - run: | - brew install opencascade + run: brew install opencascade - name: Install Assimp - run: | - brew install assimp + run: brew install assimp - name: Get count of CPU cores uses: SimenB/github-actions-cpu-cores@v1 @@ -35,7 +30,8 @@ jobs: cmake --version cmake .. \ -DMayo_BuildTests=ON \ - -DMayo_BuildPluginAssimp=ON + -DMayo_BuildPluginAssimp=ON \ + -DQT_DIR=$(brew --prefix qt5)/lib/cmake/Qt5 cmake --build . \ --config Release \ --parallel ${{steps.cpu-cores.outputs.count}} From fc7115aef54ac95f12114acafefa36b656585361 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Wed, 15 Mar 2023 11:39:06 +0100 Subject: [PATCH 064/101] Use new OccHandle<> type alias for OpenCascade handles This replaces all occurences of Handle(), Handle_, and opencascade::handle<> --- src/app/dialog_inspect_xde.cpp | 37 ++++++++++--------- src/app/dialog_inspect_xde.h | 5 ++- src/app/dialog_save_image_view.cpp | 2 +- src/app/dialog_save_image_view.h | 5 ++- ...ocument_tree_node_properties_providers.cpp | 4 +- src/app/main.cpp | 4 +- src/app/widget_clip_planes.cpp | 8 ++-- src/app/widget_clip_planes.h | 7 ++-- src/app/widget_gui_document.cpp | 2 +- src/app/widget_occ_view.cpp | 36 +++++++++--------- src/app/widget_occ_view.h | 24 ++++++------ src/app/widget_occ_view_impl.cpp | 25 +++++++------ src/base/application.cpp | 20 +++++----- src/base/application.h | 11 +++--- src/base/application_ptr.h | 3 +- src/base/brep_utils.cpp | 6 +-- src/base/brep_utils.h | 3 +- src/base/caf_utils.cpp | 4 +- src/base/caf_utils.h | 7 ++-- src/base/document.cpp | 2 +- src/base/document_ptr.h | 3 +- src/base/mesh_access.cpp | 8 ++-- src/base/mesh_access.h | 3 +- src/base/mesh_utils.cpp | 22 ++++++----- src/base/mesh_utils.h | 16 ++++---- src/base/point_cloud_data.cpp | 8 ++-- src/base/point_cloud_data.h | 16 ++++---- src/base/string_conv.h | 21 ++++++----- src/base/tkernel_utils.cpp | 2 +- src/base/tkernel_utils.h | 8 ++-- src/base/triangulation_annex_data.cpp | 6 +-- src/base/triangulation_annex_data.h | 9 +++-- src/base/xcaf.cpp | 26 ++++++------- src/base/xcaf.h | 16 ++++---- src/cli/main.cpp | 4 +- src/graphics/ais_text.cpp | 14 +++---- src/graphics/ais_text.h | 14 +++---- src/graphics/graphics_create_driver.cpp | 6 ++- .../graphics_create_virtual_window.cpp | 6 ++- src/graphics/graphics_mesh_data_source.cpp | 5 ++- src/graphics/graphics_mesh_data_source.h | 12 +++--- src/graphics/graphics_mesh_object_driver.cpp | 18 ++++----- src/graphics/graphics_mesh_object_driver.h | 2 +- src/graphics/graphics_object_driver.h | 2 +- src/graphics/graphics_object_ptr.h | 3 +- src/graphics/graphics_owner_ptr.h | 3 +- .../graphics_point_cloud_object_driver.h | 2 +- src/graphics/graphics_scene.cpp | 24 ++++++------ src/graphics/graphics_scene.h | 2 +- src/graphics/graphics_shape_object_driver.cpp | 2 +- src/graphics/graphics_shape_object_driver.h | 2 +- src/graphics/graphics_utils.cpp | 2 +- src/graphics/graphics_utils.h | 17 +++++---- src/gui/gui_document.cpp | 20 +++++----- src/gui/gui_document.h | 8 ++-- src/gui/v3d_view_camera_animation.cpp | 8 ++-- src/gui/v3d_view_camera_animation.h | 19 +++++----- src/gui/v3d_view_controller.cpp | 2 +- src/gui/v3d_view_controller.h | 7 ++-- src/io_dxf/io_dxf.cpp | 20 +++++----- src/io_gmio/io_gmio_amf_writer.cpp | 2 +- src/io_gmio/io_gmio_amf_writer.h | 2 +- src/io_image/io_image.cpp | 18 ++++----- src/io_image/io_image.h | 6 +-- src/io_occ/io_occ_base_mesh.cpp | 2 +- src/io_occ/io_occ_brep.cpp | 6 +-- src/io_occ/io_occ_caf.cpp | 16 ++++---- src/io_occ/io_occ_caf.h | 8 ++-- src/io_occ/io_occ_gltf_writer.cpp | 2 +- src/io_occ/io_occ_obj_writer.cpp | 2 +- src/io_occ/io_occ_step.cpp | 14 ++++--- src/io_occ/io_occ_stl.cpp | 4 +- src/io_occ/io_occ_stl.h | 3 +- src/io_off/io_off_reader.cpp | 2 +- src/io_off/io_off_writer.cpp | 4 +- src/io_ply/io_ply_reader.cpp | 2 +- src/io_ply/io_ply_writer.cpp | 4 +- src/measure/measure_display.cpp | 6 +-- src/measure/measure_display.h | 32 ++++++++-------- src/measure/measure_tool_brep.cpp | 7 ++-- src/qtcommon/qstring_conv.h | 8 ++-- tests/test_base.cpp | 6 +-- 82 files changed, 400 insertions(+), 359 deletions(-) diff --git a/src/app/dialog_inspect_xde.cpp b/src/app/dialog_inspect_xde.cpp index 0edcc896..94ebaa9d 100644 --- a/src/app/dialog_inspect_xde.cpp +++ b/src/app/dialog_inspect_xde.cpp @@ -9,6 +9,7 @@ #include "../base/application.h" #include "../base/brep_utils.h" #include "../base/caf_utils.h" +#include "../base/occ_handle.h" #include "../base/meta_enum.h" #include "../base/settings.h" #include "../base/tkernel_utils.h" @@ -80,7 +81,7 @@ static QStringUtils::TextOptions appDefaultTextOptions() static void loadLabelAttributes(const TDF_Label& label, QTreeWidgetItem* treeItem) { for (TDF_AttributeIterator it(label); it.More(); it.Next()) { - const Handle_TDF_Attribute ptrAttr = it.Value(); + const OccHandle ptrAttr = it.Value(); const Standard_GUID& attrId = ptrAttr->ID(); QString text; QString value; @@ -198,7 +199,7 @@ static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const QStrin return itemProperty; } -static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const Handle(TCollection_HAsciiString)& value) +static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const OccHandle& value) { return createPropertyTreeItem(text, to_QString(value)); } @@ -264,7 +265,7 @@ static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const TopoDS } static void loadLabelMaterialProperties( - const TDF_Label& label, const Handle_XCAFDoc_MaterialTool& materialTool, QTreeWidgetItem* treeItem) + const TDF_Label& label, const OccHandle& materialTool, QTreeWidgetItem* treeItem) { QList listItemProp; auto fnAddItem = [&](QTreeWidgetItem* item) { listItemProp.push_back(item); }; @@ -273,11 +274,11 @@ static void loadLabelMaterialProperties( QStringUtils::TextOptions densityValueTextOptions = appDefaultTextOptions(); densityValueTextOptions.unitDecimals = 6; - Handle(TCollection_HAsciiString) name; - Handle(TCollection_HAsciiString) description; + OccHandle name; + OccHandle description; double density; - Handle(TCollection_HAsciiString) densityName; - Handle(TCollection_HAsciiString) densityValueType; + OccHandle densityName; + OccHandle densityValueType; if (materialTool->GetMaterial(label, name, description, density, densityName, densityValueType)) { fnAddItem(createPropertyTreeItem("Name", to_QString(name))); fnAddItem(createPropertyTreeItem("Description", to_QString(description))); @@ -397,7 +398,7 @@ class ImageFileTreeWidgetItem : public QTreeWidgetItem { mutable std::unordered_map m_mapColumnItemData; }; -static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const Handle(Image_Texture)& imgTexture) +static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const OccHandle& imgTexture) { if (imgTexture.IsNull()) return static_cast(nullptr); @@ -439,7 +440,7 @@ static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const Handle #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) static void loadLabelVisMaterialProperties( const TDF_Label& label, - const Handle_XCAFDoc_VisMaterialTool& visMaterialTool, + const OccHandle& visMaterialTool, QTreeWidgetItem* treeItem) { QList listItemProp; @@ -447,7 +448,7 @@ static void loadLabelVisMaterialProperties( fnAddItem(createPropertyTreeItem("IsMaterial", visMaterialTool->IsMaterial(label))); fnAddItem(createPropertyTreeItem("IsSetShapeMaterial", visMaterialTool->IsSetShapeMaterial(label))); - auto fnCreateVisMaterialTreeItem = [](const QString& text, const Handle_XCAFDoc_VisMaterial& material) { + auto fnCreateVisMaterialTreeItem = [](const QString& text, const OccHandle& material) { auto item = new QTreeWidgetItem; item->setText(0, text); item->addChild(createPropertyTreeItem("HasPbrMaterial", material->HasPbrMaterial())); @@ -499,13 +500,13 @@ static void loadLabelVisMaterialProperties( }; if (visMaterialTool->IsMaterial(label)) { - Handle_XCAFDoc_VisMaterial visMaterial = visMaterialTool->GetMaterial(label); + OccHandle visMaterial = visMaterialTool->GetMaterial(label); if (!visMaterial.IsNull() && !visMaterial->IsEmpty()) fnAddItem(fnCreateVisMaterialTreeItem("Material", visMaterial)); } if (visMaterialTool->IsSetShapeMaterial(label)) { - Handle_XCAFDoc_VisMaterial visMaterial = visMaterialTool->GetShapeMaterial(label); + OccHandle visMaterial = visMaterialTool->GetShapeMaterial(label); if (!visMaterial.IsNull() && !visMaterial->IsEmpty()) fnAddItem(fnCreateVisMaterialTreeItem("ShapeMaterial", visMaterial)); } @@ -516,7 +517,7 @@ static void loadLabelVisMaterialProperties( static void loadLabelColorProperties( const TDF_Label& label, - const Handle_XCAFDoc_ColorTool& colorTool, + const OccHandle& colorTool, QTreeWidgetItem* treeItem) { QList listItemProp; @@ -545,7 +546,7 @@ static void loadLabelColorProperties( static void loadLabelShapeProperties( const TDF_Label& label, - const Handle_XCAFDoc_ShapeTool& shapeTool, + const OccHandle& shapeTool, QTreeWidgetItem* treeItem) { QList listItemProp; @@ -588,7 +589,7 @@ static void loadLabelDimensionProperties(const TDF_Label& label, QTreeWidgetItem if (!dimAttr) return; - Handle_XCAFDimTolObjects_DimensionObject dimObject = dimAttr->GetObject(); + OccHandle dimObject = dimAttr->GetObject(); fnAddItem(createPropertyTreeItem("SemanticName", dimObject->GetSemanticName())); fnAddItem(createPropertyTreeItem( "Qualifier", MetaEnum::nameWithoutPrefix(dimObject->GetQualifier(), "XCAFDimTolObjects_"))); @@ -659,7 +660,7 @@ static void loadLabelDatumProperties(const TDF_Label& label, QTreeWidgetItem* tr if (!datumAttr) return; - Handle_XCAFDimTolObjects_DatumObject datumObject = datumAttr->GetObject(); + OccHandle datumObject = datumAttr->GetObject(); fnAddItem(createPropertyTreeItem("SemanticName", datumObject->GetSemanticName())); fnAddItem(createPropertyTreeItem("Name", datumObject->GetName())); { @@ -712,7 +713,7 @@ static void loadLabelGeomToleranceProperties(const TDF_Label& label, QTreeWidget if (!tolAttr) return; - Handle_XCAFDimTolObjects_GeomToleranceObject tolObject = tolAttr->GetObject(); + OccHandle tolObject = tolAttr->GetObject(); fnAddItem(createPropertyTreeItem("SemanticName", tolObject->GetSemanticName())); fnAddItem(createPropertyTreeItem( "Type", MetaEnum::nameWithoutPrefix(tolObject->GetType(), "XCAFDimTolObjects_"))); @@ -796,7 +797,7 @@ DialogInspectXde::~DialogInspectXde() delete m_ui; } -void DialogInspectXde::load(const Handle_TDocStd_Document& doc) +void DialogInspectXde::load(const OccHandle& doc) { m_doc = doc; if (!XCAFDoc_DocumentTool::IsXCAFDocument(doc)) { diff --git a/src/app/dialog_inspect_xde.h b/src/app/dialog_inspect_xde.h index b65c8b6c..cc4a9a5f 100644 --- a/src/app/dialog_inspect_xde.h +++ b/src/app/dialog_inspect_xde.h @@ -6,6 +6,7 @@ #pragma once +#include "../base/occ_handle.h" #include #include class QTreeWidgetItem; @@ -18,13 +19,13 @@ class DialogInspectXde : public QDialog { DialogInspectXde(QWidget* parent = nullptr); ~DialogInspectXde(); - void load(const Handle_TDocStd_Document& doc); + void load(const OccHandle& doc); private: void onLabelTreeWidgetItemClicked(QTreeWidgetItem* item, int column); class Ui_DialogInspectXde* m_ui = nullptr; - Handle_TDocStd_Document m_doc; + OccHandle m_doc; }; } // namespace Mayo diff --git a/src/app/dialog_save_image_view.cpp b/src/app/dialog_save_image_view.cpp index 960229ee..4413ffa4 100644 --- a/src/app/dialog_save_image_view.cpp +++ b/src/app/dialog_save_image_view.cpp @@ -33,7 +33,7 @@ static QImage qtImageTemp(const Image_PixMap& occImg) } // namespace Internal -DialogSaveImageView::DialogSaveImageView(const Handle_V3d_View& view, QWidget* parent) +DialogSaveImageView::DialogSaveImageView(const OccHandle& view, QWidget* parent) : QDialog(parent), m_ui(new Ui_DialogSaveImageView), m_view(view) diff --git a/src/app/dialog_save_image_view.h b/src/app/dialog_save_image_view.h index 66162971..7970fe49 100644 --- a/src/app/dialog_save_image_view.h +++ b/src/app/dialog_save_image_view.h @@ -6,6 +6,7 @@ #pragma once +#include "../base/occ_handle.h" #include #include @@ -16,7 +17,7 @@ namespace Mayo { class DialogSaveImageView : public QDialog { Q_OBJECT public: - DialogSaveImageView(const Handle_V3d_View& view, QWidget* parent = nullptr); + DialogSaveImageView(const OccHandle& view, QWidget* parent = nullptr); ~DialogSaveImageView(); private: @@ -27,7 +28,7 @@ class DialogSaveImageView : public QDialog { bool createImageView(Image_PixMap* img) const; class Ui_DialogSaveImageView* m_ui = nullptr; - Handle_V3d_View m_view; + OccHandle m_view; }; } // namespace Mayo diff --git a/src/app/document_tree_node_properties_providers.cpp b/src/app/document_tree_node_properties_providers.cpp index 70f7950f..8e59aa53 100644 --- a/src/app/document_tree_node_properties_providers.cpp +++ b/src/app/document_tree_node_properties_providers.cpp @@ -104,7 +104,7 @@ class XCaf_DocumentTreeNodePropertiesProvider::Properties : public PropertyGroup // Material { const TDF_Label labelPart = XCaf::isShapeReference(label) ? XCaf::shapeReferred(label) : label; - const Handle_XCAFDoc_Material material = XCaf::shapeMaterial(labelPart); + const OccHandle material = XCaf::shapeMaterial(labelPart); if (material) { m_propertyMaterialDensity.setQuantity(XCaf::shapeMaterialDensity(material)); m_propertyMaterialName.setValue(to_stdString(material->GetName())); @@ -219,7 +219,7 @@ class Mesh_DocumentTreeNodePropertiesProvider::Properties : public PropertyGroup public: Properties(const DocumentTreeNode& treeNode) { - Handle_Poly_Triangulation mesh; + OccHandle mesh; IMeshAccess_visitMeshes(treeNode, [&](const IMeshAccess& access) { mesh = access.triangulation(); }); diff --git a/src/app/main.cpp b/src/app/main.cpp index b08d860c..c06e0ce2 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -65,7 +65,7 @@ namespace Mayo { // Declared in graphics/graphics_create_driver.cpp -void setFunctionCreateGraphicsDriver(std::function fn); +void setFunctionCreateGraphicsDriver(std::function()> fn); // Provides an i18n context for the current file(main.cpp) class Main { @@ -267,7 +267,7 @@ Thumbnail createGuiDocumentThumbnail(GuiDocument* guiDoc, QSize size) params.width = size.width(); params.height = size.height(); params.backgroundColor = QtGuiUtils::toPreferredColorSpace(mayoTheme()->color(Theme::Color::Palette_Window)); - Handle_Image_AlienPixMap pixmap = IO::ImageWriter::createImage(guiDoc, params); + OccHandle pixmap = IO::ImageWriter::createImage(guiDoc, params); if (!pixmap) { qDebug() << "Empty pixmap returned by IO::ImageWriter::createImage()"; return thumbnail; diff --git a/src/app/widget_clip_planes.cpp b/src/app/widget_clip_planes.cpp index fd2387e7..c2e5889a 100644 --- a/src/app/widget_clip_planes.cpp +++ b/src/app/widget_clip_planes.cpp @@ -70,7 +70,7 @@ WidgetClipPlanes::WidgetClipPlanes(GraphicsViewPtr view, QWidget* parent) m_view.redraw(); } else if (property == &appModule->properties()->clipPlanesCappingHatchOn) { - Handle_Graphic3d_TextureMap hatchTexture; + OccHandle hatchTexture; if (m_textureCapping && appModule->properties()->clipPlanesCappingHatchOn) hatchTexture = m_textureCapping; @@ -116,7 +116,7 @@ void WidgetClipPlanes::setClippingOn(bool on) void WidgetClipPlanes::connectUi(ClipPlaneData* data) { UiClipPlane& ui = data->ui; - const Handle_Graphic3d_ClipPlane& gfx = data->graphics; + const OccHandle& gfx = data->graphics; QAbstractSlider* posSlider = ui.posSlider(); QDoubleSpinBox* posSpin = ui.posSpin(); auto signalSpinValueChanged = qOverload(&QDoubleSpinBox::valueChanged); @@ -201,7 +201,7 @@ void WidgetClipPlanes::connectUi(ClipPlaneData* data) } } -void WidgetClipPlanes::setPlaneOn(const Handle_Graphic3d_ClipPlane& plane, bool on) +void WidgetClipPlanes::setPlaneOn(const OccHandle& plane, bool on) { plane->SetOn(on); if (!GraphicsUtils::V3dView_hasClipPlane(m_view.v3dView(), plane)) @@ -242,7 +242,7 @@ void WidgetClipPlanes::createPlaneCappingTexture() const QByteArray fileContents = file.readAll(); const QByteArray filenameUtf8 = file.fileName().toUtf8(); auto fileContentsData = reinterpret_cast(fileContents.constData()); - Handle_Image_AlienPixMap imageCapping = new Image_AlienPixMap; + OccHandle imageCapping = new Image_AlienPixMap; imageCapping->Load(fileContentsData, fileContents.size(), filenameUtf8.constData()); m_textureCapping = new GraphicsTexture2D(imageCapping); m_textureCapping->EnableModulate(); diff --git a/src/app/widget_clip_planes.h b/src/app/widget_clip_planes.h index 13b15625..40cf0f96 100644 --- a/src/app/widget_clip_planes.h +++ b/src/app/widget_clip_planes.h @@ -6,6 +6,7 @@ #pragma once +#include "../base/occ_handle.h" #include "../graphics/graphics_view_ptr.h" #include @@ -47,7 +48,7 @@ class WidgetClipPlanes : public QWidget { }; struct ClipPlaneData { - Handle_Graphic3d_ClipPlane graphics; + OccHandle graphics; UiClipPlane ui; }; @@ -55,7 +56,7 @@ class WidgetClipPlanes : public QWidget { void connectUi(ClipPlaneData* data); - void setPlaneOn(const Handle_Graphic3d_ClipPlane& plane, bool on); + void setPlaneOn(const OccHandle& plane, bool on); void setPlaneRange(ClipPlaneData* data, const Range& range); void createPlaneCappingTexture(); @@ -64,7 +65,7 @@ class WidgetClipPlanes : public QWidget { GraphicsViewPtr m_view; std::vector m_vecClipPlaneData; Bnd_Box m_bndBox; - Handle_Graphic3d_TextureMap m_textureCapping; + OccHandle m_textureCapping; }; } // namespace Mayo diff --git a/src/app/widget_gui_document.cpp b/src/app/widget_gui_document.cpp index 71e56a34..87218f58 100644 --- a/src/app/widget_gui_document.cpp +++ b/src/app/widget_gui_document.cpp @@ -154,7 +154,7 @@ WidgetGuiDocument::WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent) }); m_guiDoc->viewCameraAnimation()->setBackend(std::make_unique(QEasingCurve::OutExpo)); - m_guiDoc->viewCameraAnimation()->setRenderFunction([=](const Handle_V3d_View& view){ + m_guiDoc->viewCameraAnimation()->setRenderFunction([=](const OccHandle& view){ if (view == m_qtOccView->v3dView()) m_qtOccView->redraw(); }); diff --git a/src/app/widget_occ_view.cpp b/src/app/widget_occ_view.cpp index f907f500..84d6c201 100644 --- a/src/app/widget_occ_view.cpp +++ b/src/app/widget_occ_view.cpp @@ -9,8 +9,8 @@ # include #endif +#include "../base/occ_handle.h" #include "widget_occ_view.h" - #include "occt_window.h" #include @@ -22,7 +22,7 @@ namespace Mayo { static IWidgetOccView::Creator& getWidgetOccViewCreator() { - static IWidgetOccView::Creator fn = [](const Handle_V3d_View& view, QWidget* parent) { + static IWidgetOccView::Creator fn = [](const OccHandle& view, QWidget* parent) { return new QWidgetOccView(view, parent); }; return fn; @@ -33,7 +33,7 @@ void IWidgetOccView::setCreator(IWidgetOccView::Creator fn) getWidgetOccViewCreator() = std::move(fn); } -IWidgetOccView* IWidgetOccView::create(const Handle_V3d_View& view, QWidget* parent) +IWidgetOccView* IWidgetOccView::create(const OccHandle& view, QWidget* parent) { const auto& fn = getWidgetOccViewCreator(); return fn(view, parent); @@ -44,12 +44,12 @@ IWidgetOccView* IWidgetOccView::create(const Handle_V3d_View& view, QWidget* par // Defined in widget_occ_view.cpp bool QOpenGLWidgetOccView_isCoreProfile(); void QOpenGLWidgetOccView_createOpenGlContext(std::function fnCallback); -Handle_Graphic3d_GraphicDriver QOpenGLWidgetOccView_createCompatibleGraphicsDriver(); -bool QOpenGLWidgetOccView_wrapFrameBuffer(const Handle_Graphic3d_GraphicDriver&); -Graphic3d_Vec2i QOpenGLWidgetOccView_getDefaultframeBufferViewportSize(const Handle_Graphic3d_GraphicDriver&); +OccHandle QOpenGLWidgetOccView_createCompatibleGraphicsDriver(); +bool QOpenGLWidgetOccView_wrapFrameBuffer(const OccHandle&); +Graphic3d_Vec2i QOpenGLWidgetOccView_getDefaultframeBufferViewportSize(const OccHandle&); -static Handle_Aspect_NeutralWindow createNativeWindow([[maybe_unused]] QWidget* widget) +static OccHandle createNativeWindow([[maybe_unused]] QWidget* widget) { auto window = new Aspect_NeutralWindow; // On non-Windows systems Aspect_Drawable is aliased to 'unsigned long' so can't init with nullptr @@ -65,7 +65,7 @@ static Handle_Aspect_NeutralWindow createNativeWindow([[maybe_unused]] QWidget* return window; } -QOpenGLWidgetOccView::QOpenGLWidgetOccView(const Handle_V3d_View& view, QWidget* parent) +QOpenGLWidgetOccView::QOpenGLWidgetOccView(const OccHandle& view, QWidget* parent) : QOpenGLWidget(parent), IWidgetOccView(view) { @@ -100,12 +100,12 @@ void QOpenGLWidgetOccView::redraw() this->update(); } -QOpenGLWidgetOccView* QOpenGLWidgetOccView::create(const Handle_V3d_View& view, QWidget* parent) +QOpenGLWidgetOccView* QOpenGLWidgetOccView::create(const OccHandle& view, QWidget* parent) { return new QOpenGLWidgetOccView(view, parent); } -Handle_Graphic3d_GraphicDriver QOpenGLWidgetOccView::createCompatibleGraphicsDriver() +OccHandle QOpenGLWidgetOccView::createCompatibleGraphicsDriver() { return QOpenGLWidgetOccView_createCompatibleGraphicsDriver(); } @@ -115,7 +115,7 @@ void QOpenGLWidgetOccView::initializeGL() const QRect wrect = this->rect(); const Graphic3d_Vec2i viewSize(wrect.right() - wrect.left(), wrect.bottom() - wrect.top()); QOpenGLWidgetOccView_createOpenGlContext([=](Aspect_RenderingContext context) { - auto window = Handle_Aspect_NeutralWindow::DownCast(this->v3dView()->Window()); + auto window = OccHandle::DownCast(this->v3dView()->Window()); if (!window) window = createNativeWindow(this); @@ -129,13 +129,13 @@ void QOpenGLWidgetOccView::paintGL() if (!this->v3dView()->Window()) return; - const Handle(Graphic3d_GraphicDriver)& driver = this->v3dView()->Viewer()->Driver(); + const OccHandle& driver = this->v3dView()->Viewer()->Driver(); if (!QOpenGLWidgetOccView_wrapFrameBuffer(driver)) return; Graphic3d_Vec2i viewSizeOld; const Graphic3d_Vec2i viewSizeNew = QOpenGLWidgetOccView_getDefaultframeBufferViewportSize(driver); - auto window = Handle_Aspect_NeutralWindow::DownCast(this->v3dView()->Window()); + auto window = OccHandle::DownCast(this->v3dView()->Window()); window->Size(viewSizeOld.x(), viewSizeOld.y()); if (viewSizeNew != viewSizeOld) { window->SetSize(viewSizeNew.x(), viewSizeNew.y()); @@ -151,7 +151,7 @@ void QOpenGLWidgetOccView::paintGL() #endif // OCC_VERSION_HEX >= 0x070600 -QWidgetOccView::QWidgetOccView(const Handle_V3d_View& view, QWidget* parent) +QWidgetOccView::QWidgetOccView(const OccHandle& view, QWidget* parent) : QWidget(parent), IWidgetOccView(view) { @@ -165,8 +165,8 @@ QWidgetOccView::QWidgetOccView(const Handle_V3d_View& view, QWidget* parent) } // Defined in widget_occ_view.cpp -Handle_Graphic3d_GraphicDriver QWidgetOccView_createCompatibleGraphicsDriver(); -Handle_Graphic3d_GraphicDriver QWidgetOccView::createCompatibleGraphicsDriver() +OccHandle QWidgetOccView_createCompatibleGraphicsDriver(); +OccHandle QWidgetOccView::createCompatibleGraphicsDriver() { return QWidgetOccView_createCompatibleGraphicsDriver(); } @@ -176,7 +176,7 @@ void QWidgetOccView::redraw() this->v3dView()->Redraw(); } -QWidgetOccView* QWidgetOccView::create(const Handle_V3d_View& view, QWidget* parent) +QWidgetOccView* QWidgetOccView::create(const OccHandle& view, QWidget* parent) { return new QWidgetOccView(view, parent); } @@ -184,7 +184,7 @@ QWidgetOccView* QWidgetOccView::create(const Handle_V3d_View& view, QWidget* par void QWidgetOccView::showEvent(QShowEvent*) { if (this->v3dView()->Window().IsNull()) { - Handle_Aspect_Window hWnd = new OcctWindow(this); + OccHandle hWnd = new OcctWindow(this); this->v3dView()->SetWindow(hWnd); if (!hWnd->IsMapped()) hWnd->Map(); diff --git a/src/app/widget_occ_view.h b/src/app/widget_occ_view.h index 0f7f3e8a..0151c2ed 100644 --- a/src/app/widget_occ_view.h +++ b/src/app/widget_occ_view.h @@ -6,6 +6,8 @@ #pragma once +#include "../base/occ_handle.h" + #include #include #include @@ -23,21 +25,21 @@ namespace Mayo { // IWidgetOccView does not handle input devices interaction like keyboard and mouse class IWidgetOccView { public: - const Handle_V3d_View& v3dView() const { return m_view; } + const OccHandle& v3dView() const { return m_view; } virtual void redraw() = 0; virtual QWidget* widget() = 0; virtual bool supportsWidgetOpacity() const = 0; - using Creator = std::function; + using Creator = std::function&, QWidget*)>; static void setCreator(Creator fn); - static IWidgetOccView* create(const Handle_V3d_View& view, QWidget* parent = nullptr); + static IWidgetOccView* create(const OccHandle& view, QWidget* parent = nullptr); protected: - IWidgetOccView(const Handle_V3d_View& view) : m_view(view) {} + IWidgetOccView(const OccHandle& view) : m_view(view) {} private: - Handle_V3d_View m_view; + OccHandle m_view; }; #if OCC_VERSION_HEX >= 0x070600 @@ -46,14 +48,14 @@ class IWidgetOccView { // QOpenGLWidgetOccView implementation is based on https://github.com/gkv311/occt-samples-qopenglwidget class QOpenGLWidgetOccView : public QOpenGLWidget, public IWidgetOccView { public: - QOpenGLWidgetOccView(const Handle_V3d_View& view, QWidget* parent = nullptr); + QOpenGLWidgetOccView(const OccHandle& view, QWidget* parent = nullptr); void redraw() override; QWidget* widget() override { return this; } bool supportsWidgetOpacity() const override { return true; } - static QOpenGLWidgetOccView* create(const Handle_V3d_View& view, QWidget* parent); - static Handle_Graphic3d_GraphicDriver createCompatibleGraphicsDriver(); + static QOpenGLWidgetOccView* create(const OccHandle& view, QWidget* parent); + static OccHandle createCompatibleGraphicsDriver(); protected: // -- QOpenGLWidget @@ -65,14 +67,14 @@ class QOpenGLWidgetOccView : public QOpenGLWidget, public IWidgetOccView { // Fallback using traditional QWidget wrapper, no translucid background support class QWidgetOccView : public QWidget, public IWidgetOccView { public: - QWidgetOccView(const Handle_V3d_View& view, QWidget* parent = nullptr); + QWidgetOccView(const OccHandle& view, QWidget* parent = nullptr); void redraw() override; QWidget* widget() override { return this; } bool supportsWidgetOpacity() const override { return false; } - static QWidgetOccView* create(const Handle_V3d_View& view, QWidget* parent); - static Handle_Graphic3d_GraphicDriver createCompatibleGraphicsDriver(); + static QWidgetOccView* create(const OccHandle& view, QWidget* parent); + static OccHandle createCompatibleGraphicsDriver(); protected: // -- QWidget diff --git a/src/app/widget_occ_view_impl.cpp b/src/app/widget_occ_view_impl.cpp index 682d7925..9a61c541 100644 --- a/src/app/widget_occ_view_impl.cpp +++ b/src/app/widget_occ_view_impl.cpp @@ -4,6 +4,7 @@ ** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt ****************************************************************************/ +#include "../base/occ_handle.h" #include "../graphics/graphics_utils.h" #include @@ -34,20 +35,20 @@ class QtOccFrameBuffer : public OpenGl_FrameBuffer { public: QtOccFrameBuffer() {} - void BindBuffer(const Handle(OpenGl_Context)& ctx) override + void BindBuffer(const OccHandle& ctx) override { OpenGl_FrameBuffer::BindBuffer(ctx); ctx->SetFrameBufferSRGB(true, false); // NOTE: commenting the line just above makes the FBO to work on some configs(eg VM Ubuntu 18.04) } - void BindDrawBuffer(const Handle(OpenGl_Context)& ctx) override + void BindDrawBuffer(const OccHandle& ctx) override { OpenGl_FrameBuffer::BindDrawBuffer(ctx); ctx->SetFrameBufferSRGB(true, false); } - void BindReadBuffer(const Handle(OpenGl_Context)& ctx) override + void BindReadBuffer(const OccHandle& ctx) override { OpenGl_FrameBuffer::BindReadBuffer(ctx); } @@ -62,7 +63,7 @@ bool QOpenGLWidgetOccView_isCoreProfile() void QOpenGLWidgetOccView_createOpenGlContext(std::function fnCallback) { - Handle_OpenGl_Context glCtx = new OpenGl_Context; + OccHandle glCtx = new OpenGl_Context; if (!glCtx->Init(QOpenGLWidgetOccView_isCoreProfile())) { Message::SendFail() << "Error: OpenGl_Context is unable to wrap OpenGL context"; return; @@ -72,7 +73,7 @@ void QOpenGLWidgetOccView_createOpenGlContext(std::functionRenderingContext()); } -Handle_Graphic3d_GraphicDriver QOpenGLWidgetOccView_createCompatibleGraphicsDriver() +OccHandle QOpenGLWidgetOccView_createCompatibleGraphicsDriver() { auto gfxDriver = new OpenGl_GraphicDriver(GraphicsUtils::AspectDisplayConnection_create(), false/*dontInit*/); // Let QOpenGLWidget manage buffer swap @@ -85,15 +86,15 @@ Handle_Graphic3d_GraphicDriver QOpenGLWidgetOccView_createCompatibleGraphicsDriv return gfxDriver; } -bool QOpenGLWidgetOccView_wrapFrameBuffer(const Handle_Graphic3d_GraphicDriver& gfxDriver) +bool QOpenGLWidgetOccView_wrapFrameBuffer(const OccHandle& gfxDriver) { // Wrap FBO created by QOpenGLWidget - auto driver = Handle_OpenGl_GraphicDriver::DownCast(gfxDriver); + auto driver = OccHandle::DownCast(gfxDriver); if (!driver) return false; - const Handle_OpenGl_Context& glCtx = driver->GetSharedContext(); - Handle_OpenGl_FrameBuffer defaultFbo = glCtx->DefaultFrameBuffer(); + const OccHandle& glCtx = driver->GetSharedContext(); + OccHandle defaultFbo = glCtx->DefaultFrameBuffer(); if (!defaultFbo) { //defaultFbo = new OpenGl_FrameBuffer; defaultFbo = new QtOccFrameBuffer; @@ -109,15 +110,15 @@ bool QOpenGLWidgetOccView_wrapFrameBuffer(const Handle_Graphic3d_GraphicDriver& return true; } -Graphic3d_Vec2i QOpenGLWidgetOccView_getDefaultframeBufferViewportSize(const Handle_Graphic3d_GraphicDriver& gfxDriver) +Graphic3d_Vec2i QOpenGLWidgetOccView_getDefaultframeBufferViewportSize(const OccHandle& gfxDriver) { - auto driver = Handle_OpenGl_GraphicDriver::DownCast(gfxDriver); + auto driver = OccHandle::DownCast(gfxDriver); return driver->GetSharedContext()->DefaultFrameBuffer()->GetVPSize(); } #endif // OCC_VERSION_HEX >= 0x070600 -Handle_Graphic3d_GraphicDriver QWidgetOccView_createCompatibleGraphicsDriver() +OccHandle QWidgetOccView_createCompatibleGraphicsDriver() { return new OpenGl_GraphicDriver(GraphicsUtils::AspectDisplayConnection_create()); } diff --git a/src/base/application.cpp b/src/base/application.cpp index 8df0ca90..ab953787 100644 --- a/src/base/application.cpp +++ b/src/base/application.cpp @@ -30,7 +30,7 @@ class Document::FormatBinaryRetrievalDriver : public BinXCAFDrivers_DocumentRetr FormatBinaryRetrievalDriver(const ApplicationPtr& app) : m_app(app) {} #if OCC_VERSION_HEX < OCC_VERSION_CHECK(7, 6, 0) - Handle(CDM_Document) CreateDocument() override { return new Document(m_app); } + OccHandle CreateDocument() override { return new Document(m_app); } #endif private: @@ -42,7 +42,7 @@ class Document::FormatXmlRetrievalDriver : public XmlXCAFDrivers_DocumentRetriev FormatXmlRetrievalDriver(const ApplicationPtr& app) : m_app(app) {} #if OCC_VERSION_HEX < OCC_VERSION_CHECK(7, 6, 0) - Handle(CDM_Document) CreateDocument() override { return new Document(m_app); } + OccHandle CreateDocument() override { return new Document(m_app); } #endif private: @@ -94,9 +94,9 @@ DocumentPtr Application::newDocument(Document::Format docFormat) { const char* docNameFormat = Document::toNameFormat(docFormat); #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) - Handle(CDM_Document) stdDoc; + OccHandle stdDoc; #else - Handle(TDocStd_Document) stdDoc; + OccHandle stdDoc; #endif this->NewDocument(docNameFormat, stdDoc); return DocumentPtr::DownCast(stdDoc); @@ -104,7 +104,7 @@ DocumentPtr Application::newDocument(Document::Format docFormat) DocumentPtr Application::openDocument(const FilePath& filepath, PCDM_ReaderStatus* ptrReadStatus) { - Handle_TDocStd_Document stdDoc; + OccHandle stdDoc; const PCDM_ReaderStatus readStatus = this->Open(filepathTo(filepath), stdDoc); if (ptrReadStatus) *ptrReadStatus = readStatus; @@ -116,7 +116,7 @@ DocumentPtr Application::openDocument(const FilePath& filepath, PCDM_ReaderStatu DocumentPtr Application::findDocumentByIndex(int docIndex) const { - Handle_TDocStd_Document doc; + OccHandle doc; TDocStd_Application::GetDocument(docIndex + 1, doc); return !doc.IsNull() ? DocumentPtr::DownCast(doc) : DocumentPtr(); } @@ -209,9 +209,9 @@ Span Application::envOpenCascadePaths() return arrayPathName; } #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) -void Application::NewDocument(const TCollection_ExtendedString&, Handle(CDM_Document)& outDocument) +void Application::NewDocument(const TCollection_ExtendedString&, OccHandle& outDocument) #else -void Application::NewDocument(const TCollection_ExtendedString&, Handle(TDocStd_Document)& outDocument) +void Application::NewDocument(const TCollection_ExtendedString&, OccHandle& outDocument) #endif { // TODO: check format == "mayo" if not throw exception @@ -224,9 +224,9 @@ void Application::NewDocument(const TCollection_ExtendedString&, Handle(TDocStd_ } #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) -void Application::InitDocument(const Handle(CDM_Document)& doc) const +void Application::InitDocument(const OccHandle& doc) const #else -void Application::InitDocument(const Handle(TDocStd_Document)& doc) const +void Application::InitDocument(const OccHandle& doc) const #endif { TDocStd_Application::InitDocument(doc); diff --git a/src/base/application.h b/src/base/application.h index e685d38a..d733ce04 100644 --- a/src/base/application.h +++ b/src/base/application.h @@ -8,6 +8,7 @@ #include "application_ptr.h" #include "document.h" +#include "occ_handle.h" #include "signal.h" #include "span.h" #include "text_id.h" @@ -69,16 +70,16 @@ class Application : public TDocStd_Application { public: // -- from TDocStd_Application #if OCC_VERSION_HEX >= 0x070600 - void NewDocument(const TCollection_ExtendedString& format, Handle(CDM_Document)& outDoc) override; - void InitDocument(const opencascade::handle& doc) const override; + void NewDocument(const TCollection_ExtendedString& format, OccHandle& outDoc) override; + void InitDocument(const OccHandle& doc) const override; #else - void NewDocument(const TCollection_ExtendedString& format, Handle(TDocStd_Document)& outDoc) override; - void InitDocument(const Handle(TDocStd_Document)& doc) const override; + void NewDocument(const TCollection_ExtendedString& format, OccHandle& outDoc) override; + void InitDocument(const OccHandle& doc) const override; #endif // TODO: Redefine TDocStd_Document::BeforeClose() to emit signal documentClosed // class Document : public TDocStd_Document { ... }; -// using DocumentPtr = Handle_Document +// using DocumentPtr = OccHandle // -> Can't do because PCDM_RetrievalDriver subclasses create explicitly "new TDocStd_Document(...)" // This would break TDocStd_Application::Open(...) diff --git a/src/base/application_ptr.h b/src/base/application_ptr.h index c7b0ff22..a8cbbc2a 100644 --- a/src/base/application_ptr.h +++ b/src/base/application_ptr.h @@ -6,12 +6,13 @@ #pragma once +#include "occ_handle.h" #include namespace Mayo { class Application; DEFINE_STANDARD_HANDLE(Application, TDocStd_Application) -using ApplicationPtr = opencascade::handle; +using ApplicationPtr = OccHandle; } // namespace Mayo diff --git a/src/base/brep_utils.cpp b/src/base/brep_utils.cpp index b694f5bc..4aba8bbb 100644 --- a/src/base/brep_utils.cpp +++ b/src/base/brep_utils.cpp @@ -36,7 +36,7 @@ void BRepUtils::addShape(TopoDS_Shape* ptrTargetShape, const TopoDS_Shape& shape builder.Add(*ptrTargetShape, shape); } -TopoDS_Edge BRepUtils::makeEdge(const Handle(Poly_Polygon3D)& polygon) +TopoDS_Edge BRepUtils::makeEdge(const OccHandle& polygon) { TopoDS_Edge edge; BRep_Builder builder; @@ -45,7 +45,7 @@ TopoDS_Edge BRepUtils::makeEdge(const Handle(Poly_Polygon3D)& polygon) return edge; } -TopoDS_Face BRepUtils::makeFace(const Handle(Poly_Triangulation)& mesh) +TopoDS_Face BRepUtils::makeFace(const OccHandle& mesh) { TopoDS_Face face; BRep_Builder builder; @@ -103,7 +103,7 @@ void BRepUtils::computeMesh( const TopoDS_Shape& shape, const OccBRepMeshParameters& params, TaskProgress* progress) { #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) - Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); + OccHandle indicator = new OccProgressIndicator(progress); BRepMesh_IncrementalMesh mesher(shape, params, TKernelUtils::start(indicator)); #else BRepMesh_IncrementalMesh mesher(shape, params); diff --git a/src/base/brep_utils.h b/src/base/brep_utils.h index a98dd2e1..57ae6a8f 100644 --- a/src/base/brep_utils.h +++ b/src/base/brep_utils.h @@ -7,6 +7,7 @@ #pragma once #include "occ_brep_mesh_parameters.h" +#include "occ_handle.h" #include #include @@ -32,7 +33,7 @@ struct BRepUtils { static TopoDS_Edge makeEdge(const Handle(Poly_Polygon3D)& polygon); // Creates a non-geometric TopoDS_Face wrapping triangulation 'mesh' - static TopoDS_Face makeFace(const Handle(Poly_Triangulation)& mesh); + static TopoDS_Face makeFace(const OccHandle& mesh); // Iterates with 'explorer' and executes 'fn' for each sub-shape template diff --git a/src/base/caf_utils.cpp b/src/base/caf_utils.cpp index 2a03d760..dea058b4 100644 --- a/src/base/caf_utils.cpp +++ b/src/base/caf_utils.cpp @@ -22,7 +22,7 @@ const TCollection_AsciiString& CafUtils::labelTag(const TDF_Label& label) const TCollection_ExtendedString& CafUtils::labelAttrStdName(const TDF_Label& label) { - Handle_TDataStd_Name attrName; + OccHandle attrName; if (label.FindAttribute(TDataStd_Name::GetID(), attrName)) { return attrName->Get(); } @@ -45,7 +45,7 @@ bool CafUtils::isNullOrEmpty(const TDF_Label& label) bool CafUtils::hasAttribute(const TDF_Label& label, const Standard_GUID& attrGuid) { - Handle_TDF_Attribute attr; + OccHandle attr; return label.FindAttribute(attrGuid, attr); } diff --git a/src/base/caf_utils.h b/src/base/caf_utils.h index 8863aa59..6bf924d6 100644 --- a/src/base/caf_utils.h +++ b/src/base/caf_utils.h @@ -6,6 +6,7 @@ #pragma once +#include "occ_handle.h" #include #include #include @@ -29,7 +30,7 @@ struct CafUtils { // Returns attribute of type 'AttributeType'(result may be null)? template - static opencascade::handle findAttribute(const TDF_Label& label); + static OccHandle findAttribute(const TDF_Label& label); // Is there an attribute of identifier 'attrGuid' attached to 'label'? static bool hasAttribute(const TDF_Label& label, const Standard_GUID& attrGuid); @@ -65,9 +66,9 @@ template<> struct hash { namespace Mayo { template -opencascade::handle CafUtils::findAttribute(const TDF_Label& label) +OccHandle CafUtils::findAttribute(const TDF_Label& label) { - opencascade::handle attr; + OccHandle attr; label.FindAttribute(AttributeType::GetID(), attr); return attr; } diff --git a/src/base/document.cpp b/src/base/document.cpp index 67e51a42..b660860c 100644 --- a/src/base/document.cpp +++ b/src/base/document.cpp @@ -130,7 +130,7 @@ DocumentPtr Document::findFrom(const TDF_Label& label) TDF_Label Document::newEntityLabel() { - Handle_TDF_TagSource tagSrc = CafUtils::findAttribute(this->rootLabel()); + OccHandle tagSrc = CafUtils::findAttribute(this->rootLabel()); Expects(!tagSrc.IsNull()); if (tagSrc->Get() == 0) this->rootLabel().NewChild(); // Reserve label 0:1 for XCAF Main() diff --git a/src/base/document_ptr.h b/src/base/document_ptr.h index 0e2c4011..08af04c9 100644 --- a/src/base/document_ptr.h +++ b/src/base/document_ptr.h @@ -6,12 +6,13 @@ #pragma once +#include "occ_handle.h" #include namespace Mayo { class Document; DEFINE_STANDARD_HANDLE(Document, TDocStd_Document) -using DocumentPtr = Handle(Document); +using DocumentPtr = OccHandle; } // namespace Mayo diff --git a/src/base/mesh_access.cpp b/src/base/mesh_access.cpp index f02255b7..d3fa01d6 100644 --- a/src/base/mesh_access.cpp +++ b/src/base/mesh_access.cpp @@ -66,7 +66,7 @@ class XCafFace_MeshAccess : public IMeshAccess { return m_location; } - const Handle(Poly_Triangulation)& triangulation() const override { + const OccHandle& triangulation() const override { return m_triangulation; } @@ -77,8 +77,8 @@ class XCafFace_MeshAccess : public IMeshAccess { return doc->xcaf().shapeColor(labelShape); #if OCC_VERSION_HEX >= 0x070500 - Handle_XCAFDoc_VisMaterialTool visMaterialTool = doc->xcaf().visMaterialTool(); - Handle_XCAFDoc_VisMaterial visMaterial = visMaterialTool->GetShapeMaterial(labelShape); + OccHandle visMaterialTool = doc->xcaf().visMaterialTool(); + OccHandle visMaterial = visMaterialTool->GetShapeMaterial(labelShape); if (visMaterial) return visMaterial->BaseColor().GetRGB(); #endif @@ -89,7 +89,7 @@ class XCafFace_MeshAccess : public IMeshAccess { std::optional m_faceColor; Span m_nodeColors; TopLoc_Location m_location; - Handle(Poly_Triangulation) m_triangulation; + OccHandle m_triangulation; }; void IMeshAccess_visitMeshes( diff --git a/src/base/mesh_access.h b/src/base/mesh_access.h index db2b4c87..800c3208 100644 --- a/src/base/mesh_access.h +++ b/src/base/mesh_access.h @@ -7,6 +7,7 @@ #pragma once // Base +#include "occ_handle.h" class DocumentTreeNode; // OpenCascade @@ -26,7 +27,7 @@ class IMeshAccess { public: virtual std::optional nodeColor(int i) const = 0; virtual const TopLoc_Location& location() const = 0; - virtual const Handle(Poly_Triangulation)& triangulation() const = 0; + virtual const OccHandle& triangulation() const = 0; }; // Iterates over meshes from `treeNode` and call `fnCallback` for each item. diff --git a/src/base/mesh_utils.cpp b/src/base/mesh_utils.cpp index 04a375ee..621bd78f 100644 --- a/src/base/mesh_utils.cpp +++ b/src/base/mesh_utils.cpp @@ -46,7 +46,7 @@ double triangleArea(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& p3) return 0.5 * std::sqrt(cx*cx + cy*cy + cz*cz); } -double triangulationVolume(const Handle_Poly_Triangulation& triangulation) +double triangulationVolume(const OccHandle& triangulation) { if (!triangulation) return 0; @@ -59,13 +59,14 @@ double triangulationVolume(const Handle_Poly_Triangulation& triangulation) volume += MeshUtils::triangleSignedVolume( triangulation->Node(v1).Coord(), triangulation->Node(v2).Coord(), - triangulation->Node(v3).Coord()); + triangulation->Node(v3).Coord() + ); } return std::abs(volume); } -double triangulationArea(const Handle_Poly_Triangulation& triangulation) +double triangulationArea(const OccHandle& triangulation) { if (!triangulation) return 0; @@ -78,13 +79,14 @@ double triangulationArea(const Handle_Poly_Triangulation& triangulation) area += MeshUtils::triangleArea( triangulation->Node(v1).Coord(), triangulation->Node(v2).Coord(), - triangulation->Node(v3).Coord()); + triangulation->Node(v3).Coord() + ); } return area; } -void setNode(const Handle_Poly_Triangulation& triangulation, int index, const gp_Pnt& pnt) +void setNode(const OccHandle& triangulation, int index, const gp_Pnt& pnt) { #if OCC_VERSION_HEX >= 0x070600 triangulation->SetNode(index, pnt); @@ -93,7 +95,7 @@ void setNode(const Handle_Poly_Triangulation& triangulation, int index, const gp #endif } -void setTriangle(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangle& triangle) +void setTriangle(const OccHandle& triangulation, int index, const Poly_Triangle& triangle) { #if OCC_VERSION_HEX >= 0x070600 triangulation->SetTriangle(index, triangle); @@ -102,7 +104,7 @@ void setTriangle(const Handle_Poly_Triangulation& triangulation, int index, cons #endif } -void setNormal(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangulation_NormalType& n) +void setNormal(const OccHandle& triangulation, int index, const Poly_Triangulation_NormalType& n) { #if OCC_VERSION_HEX >= 0x070600 triangulation->SetNormal(index, n); @@ -114,7 +116,7 @@ void setNormal(const Handle_Poly_Triangulation& triangulation, int index, const #endif } -void setUvNode(const Handle_Poly_Triangulation& triangulation, int index, double u, double v) +void setUvNode(const OccHandle& triangulation, int index, double u, double v) { #if OCC_VERSION_HEX >= 0x070600 triangulation->SetUVNode(index, gp_Pnt2d{u, v}); @@ -123,7 +125,7 @@ void setUvNode(const Handle_Poly_Triangulation& triangulation, int index, double #endif } -void allocateNormals(const Handle_Poly_Triangulation& triangulation) +void allocateNormals(const OccHandle& triangulation) { #if OCC_VERSION_HEX >= 0x070600 triangulation->AddNormals(); @@ -133,7 +135,7 @@ void allocateNormals(const Handle_Poly_Triangulation& triangulation) #endif } -const Poly_Array1OfTriangle& triangles(const Handle_Poly_Triangulation& triangulation) +const Poly_Array1OfTriangle& triangles(const OccHandle& triangulation) { #if OCC_VERSION_HEX < 0x070600 return triangulation->Triangles(); diff --git a/src/base/mesh_utils.h b/src/base/mesh_utils.h index 791045fc..08fa0a15 100644 --- a/src/base/mesh_utils.h +++ b/src/base/mesh_utils.h @@ -21,8 +21,8 @@ namespace MeshUtils { double triangleSignedVolume(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& p3); double triangleArea(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& p3); -double triangulationVolume(const Handle_Poly_Triangulation& triangulation); -double triangulationArea(const Handle_Poly_Triangulation& triangulation); +double triangulationVolume(const OccHandle& triangulation); +double triangulationArea(const OccHandle& triangulation); #if OCC_VERSION_HEX >= 0x070600 using Poly_Triangulation_NormalType = gp_Vec3f; @@ -30,13 +30,13 @@ using Poly_Triangulation_NormalType = gp_Vec3f; using Poly_Triangulation_NormalType = gp_Vec; #endif -void setNode(const Handle_Poly_Triangulation& triangulation, int index, const gp_Pnt& pnt); -void setTriangle(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangle& triangle); -void setNormal(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangulation_NormalType& n); -void setUvNode(const Handle_Poly_Triangulation& triangulation, int index, double u, double v); -void allocateNormals(const Handle_Poly_Triangulation& triangulation); +void setNode(const OccHandle& triangulation, int index, const gp_Pnt& pnt); +void setTriangle(const OccHandle& triangulation, int index, const Poly_Triangle& triangle); +void setNormal(const OccHandle& triangulation, int index, const Poly_Triangulation_NormalType& n); +void setUvNode(const OccHandle& triangulation, int index, double u, double v); +void allocateNormals(const OccHandle& triangulation); -const Poly_Array1OfTriangle& triangles(const Handle_Poly_Triangulation& triangulation); +const Poly_Array1OfTriangle& triangles(const OccHandle& triangulation); enum class Orientation { Unknown, diff --git a/src/base/point_cloud_data.cpp b/src/base/point_cloud_data.cpp index 05a11dce..1da1258e 100644 --- a/src/base/point_cloud_data.cpp +++ b/src/base/point_cloud_data.cpp @@ -28,7 +28,7 @@ PointCloudDataPtr PointCloudData::Set(const TDF_Label& label) return data; } -PointCloudDataPtr PointCloudData::Set(const TDF_Label& label, const Handle(Graphic3d_ArrayOfPoints)& points) +PointCloudDataPtr PointCloudData::Set(const TDF_Label& label, const OccHandle& points) { PointCloudDataPtr data = PointCloudData::Set(label); data->m_points = points; @@ -40,19 +40,19 @@ const Standard_GUID& PointCloudData::ID() const return PointCloudData::GetID(); } -void PointCloudData::Restore(const Handle(TDF_Attribute)& attribute) +void PointCloudData::Restore(const OccHandle& attribute) { auto data = PointCloudDataPtr::DownCast(attribute); if (data) m_points = data->m_points; } -Handle(TDF_Attribute) PointCloudData::NewEmpty() const +OccHandle PointCloudData::NewEmpty() const { return new PointCloudData; } -void PointCloudData::Paste(const Handle(TDF_Attribute)& into, const Handle(TDF_RelocationTable)&) const +void PointCloudData::Paste(const OccHandle& into, const OccHandle&) const { auto data = PointCloudDataPtr::DownCast(into); if (data) diff --git a/src/base/point_cloud_data.h b/src/base/point_cloud_data.h index 6c09f063..d2746af0 100644 --- a/src/base/point_cloud_data.h +++ b/src/base/point_cloud_data.h @@ -6,6 +6,8 @@ #pragma once +#include "occ_handle.h" + #include #include @@ -14,28 +16,28 @@ namespace Mayo { // Pre-declarations class PointCloudData; DEFINE_STANDARD_HANDLE(PointCloudData, TDF_Attribute) -using PointCloudDataPtr = Handle(PointCloudData); +using PointCloudDataPtr = OccHandle; // Provides a label attribute to store point cloud data class PointCloudData : public TDF_Attribute { public: static const Standard_GUID& GetID(); static PointCloudDataPtr Set(const TDF_Label& label); - static PointCloudDataPtr Set(const TDF_Label& label, const Handle(Graphic3d_ArrayOfPoints)& points); + static PointCloudDataPtr Set(const TDF_Label& label, const OccHandle& points); - const Handle(Graphic3d_ArrayOfPoints)& points() const { return m_points; } + const OccHandle& points() const { return m_points; } // -- from TDF_Attribute const Standard_GUID& ID() const override; - void Restore(const Handle(TDF_Attribute)& attribute) override; - Handle(TDF_Attribute) NewEmpty() const override; - void Paste(const Handle(TDF_Attribute)& into, const Handle(TDF_RelocationTable)& table) const override; + void Restore(const OccHandle& attribute) override; + OccHandle NewEmpty() const override; + void Paste(const OccHandle& into, const OccHandle& table) const override; Standard_OStream& Dump(Standard_OStream& ostr) const override; DEFINE_STANDARD_RTTI_INLINE(PointCloudData, TDF_Attribute) private: - Handle(Graphic3d_ArrayOfPoints) m_points; + OccHandle m_points; }; } // namespace Mayo diff --git a/src/base/string_conv.h b/src/base/string_conv.h index 6cbd7e0d..f1e134d2 100644 --- a/src/base/string_conv.h +++ b/src/base/string_conv.h @@ -6,6 +6,7 @@ #pragma once +#include "occ_handle.h" #include #include #include @@ -52,8 +53,8 @@ TCollection_AsciiString to_OccAsciiString(const StringType& str) { // X -> Handle(TCollection_HAsciiString) template -Handle(TCollection_HAsciiString) to_OccHandleHAsciiString(const StringType& str) { - return string_conv(str); +OccHandle to_OccHandleHAsciiString(const StringType& str) { + return string_conv>(str); } // X -> TCollection_ExtendedString @@ -132,9 +133,9 @@ template<> struct StringConv { }; // std::string_view -> Handle(TCollection_HAsciiString) -template<> struct StringConv { +template<> struct StringConv> { static auto to(std::string_view str) { - Handle(TCollection_HAsciiString) hnd = new TCollection_HAsciiString(to_OccAsciiString(str)); + OccHandle hnd = new TCollection_HAsciiString(to_OccAsciiString(str)); return hnd; } }; @@ -151,15 +152,15 @@ template<> struct StringConv { // -- // Handle(TCollection_HAsciiString) -> std::string -template<> struct StringConv { - static auto to(const Handle(TCollection_HAsciiString)& str) { +template<> struct StringConv, std::string> { + static auto to(const OccHandle& str) { return string_conv(str ? str->String() : TCollection_AsciiString()); } }; // Handle(TCollection_HAsciiString) -> std::string_view -template<> struct StringConv { - static auto to(const Handle(TCollection_HAsciiString)& str) { +template<> struct StringConv, std::string_view> { + static auto to(const OccHandle& str) { return string_conv(str ? str->String() : TCollection_AsciiString()); } }; @@ -176,9 +177,9 @@ template<> struct StringConv { }; // std::string -> Handle(TCollection_HAsciiString) -template<> struct StringConv { +template<> struct StringConv> { static auto to(const std::string& str) { - Handle(TCollection_HAsciiString) hnd = new TCollection_HAsciiString(str.c_str()); + OccHandle hnd = new TCollection_HAsciiString(str.c_str()); return hnd; } }; diff --git a/src/base/tkernel_utils.cpp b/src/base/tkernel_utils.cpp index 201e63f6..3df933d1 100644 --- a/src/base/tkernel_utils.cpp +++ b/src/base/tkernel_utils.cpp @@ -12,7 +12,7 @@ namespace Mayo { TKernelUtils::ReturnType_StartProgressIndicator -TKernelUtils::start(const opencascade::handle& progress) +TKernelUtils::start(const OccHandle& progress) { #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) return Message_ProgressIndicator::Start(progress); diff --git a/src/base/tkernel_utils.h b/src/base/tkernel_utils.h index 4f1d97af..55678134 100644 --- a/src/base/tkernel_utils.h +++ b/src/base/tkernel_utils.h @@ -6,6 +6,8 @@ #pragma once +#include "occ_handle.h" + #include #include #include @@ -29,15 +31,15 @@ namespace Mayo { class TKernelUtils { public: template - static opencascade::handle makeHandle(const TransientType* ptr) { return ptr; } + static OccHandle makeHandle(const TransientType* ptr) { return ptr; } using ReturnType_StartProgressIndicator = #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) Message_ProgressRange; #else - const opencascade::handle&; + const OccHandle&; #endif - static ReturnType_StartProgressIndicator start(const opencascade::handle& progress); + static ReturnType_StartProgressIndicator start(const OccHandle& progress); // Encodes 'color' into hexadecimal representation with #RRGGBB format static std::string colorToHex(const Quantity_Color& color); diff --git a/src/base/triangulation_annex_data.cpp b/src/base/triangulation_annex_data.cpp index 5787cb3d..9d7f077b 100644 --- a/src/base/triangulation_annex_data.cpp +++ b/src/base/triangulation_annex_data.cpp @@ -51,19 +51,19 @@ const Standard_GUID& TriangulationAnnexData::ID() const return TriangulationAnnexData::GetID(); } -void TriangulationAnnexData::Restore(const Handle(TDF_Attribute)& attribute) +void TriangulationAnnexData::Restore(const OccHandle& attribute) { auto data = TriangulationAnnexDataPtr::DownCast(attribute); if (data) this->copyNodeColors(data->m_vecNodeColor); } -Handle(TDF_Attribute) TriangulationAnnexData::NewEmpty() const +OccHandle TriangulationAnnexData::NewEmpty() const { return new TriangulationAnnexData; } -void TriangulationAnnexData::Paste(const Handle(TDF_Attribute)& into, const Handle(TDF_RelocationTable)&) const +void TriangulationAnnexData::Paste(const OccHandle& into, const OccHandle&) const { auto data = TriangulationAnnexDataPtr::DownCast(into); if (data) diff --git a/src/base/triangulation_annex_data.h b/src/base/triangulation_annex_data.h index d8c074af..9c082327 100644 --- a/src/base/triangulation_annex_data.h +++ b/src/base/triangulation_annex_data.h @@ -6,6 +6,7 @@ #pragma once +#include "occ_handle.h" #include "span.h" #include @@ -16,7 +17,7 @@ namespace Mayo { class TriangulationAnnexData; DEFINE_STANDARD_HANDLE(TriangulationAnnexData, TDF_Attribute) -using TriangulationAnnexDataPtr = Handle(TriangulationAnnexData); +using TriangulationAnnexDataPtr = OccHandle; class TriangulationAnnexData : public TDF_Attribute { public: @@ -29,9 +30,9 @@ class TriangulationAnnexData : public TDF_Attribute { // -- from TDF_Attribute const Standard_GUID& ID() const override; - void Restore(const Handle(TDF_Attribute)& attribute) override; - Handle(TDF_Attribute) NewEmpty() const override; - void Paste(const Handle(TDF_Attribute)& into, const Handle(TDF_RelocationTable)& table) const override; + void Restore(const OccHandle& attribute) override; + OccHandle NewEmpty() const override; + void Paste(const OccHandle& into, const OccHandle& table) const override; Standard_OStream& Dump(Standard_OStream& ostr) const override; DEFINE_STANDARD_RTTI_INLINE(TriangulationAnnexData, TDF_Attribute) diff --git a/src/base/xcaf.cpp b/src/base/xcaf.cpp index 378974b3..7a892475 100644 --- a/src/base/xcaf.cpp +++ b/src/base/xcaf.cpp @@ -22,7 +22,7 @@ namespace Mayo { bool XCaf::isNull() const { - Handle_TDocStd_Document doc = TDocStd_Document::Get(m_labelMain); + OccHandle doc = TDocStd_Document::Get(m_labelMain); if (!doc.IsNull()) { if (XCAFDoc_DocumentTool::IsXCAFDocument(doc)) return false; @@ -31,28 +31,28 @@ bool XCaf::isNull() const return true; } -Handle_XCAFDoc_ShapeTool XCaf::shapeTool() const +OccHandle XCaf::shapeTool() const { return XCAFDoc_DocumentTool::ShapeTool(m_labelMain); } -Handle_XCAFDoc_LayerTool XCaf::layerTool() const +OccHandle XCaf::layerTool() const { return XCAFDoc_DocumentTool::LayerTool(m_labelMain); } -Handle_XCAFDoc_ColorTool XCaf::colorTool() const +OccHandle XCaf::colorTool() const { return XCAFDoc_DocumentTool::ColorTool(m_labelMain); } -Handle_XCAFDoc_MaterialTool XCaf::materialTool() const +OccHandle XCaf::materialTool() const { return XCAFDoc_DocumentTool::MaterialTool(m_labelMain); } #if OCC_VERSION_HEX >= 0x070500 -Handle_XCAFDoc_VisMaterialTool XCaf::visMaterialTool() const +OccHandle XCaf::visMaterialTool() const { return XCAFDoc_DocumentTool::VisMaterialTool(m_labelMain); } @@ -61,7 +61,7 @@ Handle_XCAFDoc_VisMaterialTool XCaf::visMaterialTool() const TDF_LabelSequence XCaf::topLevelFreeShapes() const { TDF_LabelSequence seq; - Handle_XCAFDoc_ShapeTool tool = this->shapeTool(); + OccHandle tool = this->shapeTool(); if (tool) tool->GetFreeShapes(seq); @@ -176,7 +176,7 @@ bool XCaf::isShapeSubOf(const TDF_Label& lbl, const TopoDS_Shape& shape) bool XCaf::hasShapeColor(const TDF_Label& lbl) const { - Handle_XCAFDoc_ColorTool tool = this->colorTool(); + OccHandle tool = this->colorTool(); if (!tool) return false; @@ -187,7 +187,7 @@ bool XCaf::hasShapeColor(const TDF_Label& lbl) const Quantity_Color XCaf::shapeColor(const TDF_Label& lbl) const { - Handle_XCAFDoc_ColorTool tool = this->colorTool(); + OccHandle tool = this->colorTool(); Quantity_Color color = {}; if (!tool) return color; @@ -271,15 +271,15 @@ QuantityDensity XCaf::shapeMaterialDensity(const TDF_Label& lbl) return XCaf::shapeMaterialDensity(XCaf::shapeMaterial(lbl)); } -QuantityDensity XCaf::shapeMaterialDensity(const Handle_XCAFDoc_Material& material) +QuantityDensity XCaf::shapeMaterialDensity(const OccHandle& material) { const double density = material ? material->GetDensity() : 0.; return density * Quantity_GramPerCubicCentimeter; } -Handle_XCAFDoc_Material XCaf::shapeMaterial(const TDF_Label& lbl) +OccHandle XCaf::shapeMaterial(const TDF_Label& lbl) { - Handle_TDataStd_TreeNode node; + OccHandle node; if (!lbl.FindAttribute(XCAFDoc::MaterialRefGUID(), node) || !node->HasFather()) return {}; @@ -291,7 +291,7 @@ XCaf::ValidationProperties XCaf::validationProperties(const TDF_Label& lbl) { ValidationProperties props = {}; for (TDF_AttributeIterator it(lbl); it.More(); it.Next()) { - const Handle_TDF_Attribute ptrAttr = it.Value(); + const OccHandle ptrAttr = it.Value(); const Standard_GUID& attrId = ptrAttr->ID(); if (&attrId == &XCAFDoc_Centroid::GetID()) { const auto& centroid = static_cast(*ptrAttr); diff --git a/src/base/xcaf.h b/src/base/xcaf.h index 72f45fdc..95f1175f 100644 --- a/src/base/xcaf.h +++ b/src/base/xcaf.h @@ -7,7 +7,9 @@ #pragma once #include "libtree.h" +#include "occ_handle.h" #include "quantity.h" + #include #include #include @@ -35,12 +37,12 @@ class XCaf { bool isNull() const; - Handle_XCAFDoc_ShapeTool shapeTool() const; - Handle_XCAFDoc_LayerTool layerTool() const; - Handle_XCAFDoc_ColorTool colorTool() const; - Handle_XCAFDoc_MaterialTool materialTool() const; + OccHandle shapeTool() const; + OccHandle layerTool() const; + OccHandle colorTool() const; + OccHandle materialTool() const; #if OCC_VERSION_HEX >= 0x070500 - Handle_XCAFDoc_VisMaterialTool visMaterialTool() const; + OccHandle visMaterialTool() const; #endif // -- @@ -102,8 +104,8 @@ class XCaf { // -- static QuantityDensity shapeMaterialDensity(const TDF_Label& lbl); - static QuantityDensity shapeMaterialDensity(const Handle_XCAFDoc_Material& material); - static Handle_XCAFDoc_Material shapeMaterial(const TDF_Label& lbl); + static QuantityDensity shapeMaterialDensity(const OccHandle& material); + static OccHandle shapeMaterial(const TDF_Label& lbl); // -- // -- XCAFDoc_LayerTool helpers diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 1ee9aa6d..bc02e489 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -53,7 +53,7 @@ namespace Mayo { // Declared in graphics/graphics_create_driver.cpp -void setFunctionCreateGraphicsDriver(std::function fn); +void setFunctionCreateGraphicsDriver(std::function()> fn); // Provides an i18n context for the current file(main.cpp) class Main { @@ -340,7 +340,7 @@ static void initGui(GuiApplication* guiApp) return; guiApp->setAutomaticDocumentMapping(false); // GuiDocument objects aren't needed - setFunctionCreateGraphicsDriver([]() -> Handle_Graphic3d_GraphicDriver { + setFunctionCreateGraphicsDriver([]() -> OccHandle { return new OpenGl_GraphicDriver(GraphicsUtils::AspectDisplayConnection_create()); }); guiApp->addGraphicsObjectDriver(std::make_unique()); diff --git a/src/graphics/ais_text.cpp b/src/graphics/ais_text.cpp index 46d7d838..b4334e3d 100644 --- a/src/graphics/ais_text.cpp +++ b/src/graphics/ais_text.cpp @@ -40,14 +40,14 @@ AIS_Text::AIS_Text(const TCollection_ExtendedString &text, const gp_Pnt& pos) this->setPosition(pos); } -Handle_Prs3d_TextAspect AIS_Text::presentationTextAspect(unsigned i) const +OccHandle AIS_Text::presentationTextAspect(unsigned i) const { - return this->isValidTextIndex(i) ? m_textProps.at(i).m_aspect : Handle_Prs3d_TextAspect(); + return this->isValidTextIndex(i) ? m_textProps.at(i).m_aspect : OccHandle(); } -Handle_Graphic3d_AspectText3d AIS_Text::graphicTextAspect(unsigned i) const +OccHandle AIS_Text::graphicTextAspect(unsigned i) const { - return this->isValidTextIndex(i) ? m_textProps.at(i).m_aspect->Aspect() : Handle_Graphic3d_AspectText3d(); + return this->isValidTextIndex(i) ? m_textProps.at(i).m_aspect->Aspect() : OccHandle(); } gp_Pnt AIS_Text::position(unsigned i) const @@ -143,8 +143,8 @@ void AIS_Text::setDefaultTextStyle(Aspect_TypeOfStyleText style) } void AIS_Text::Compute( - const Handle(PrsMgr_PresentationManager)&, - const Handle(Prs3d_Presentation)& pres, + const OccHandle&, + const OccHandle& pres, const int) { for (unsigned i = 0; i < this->textCount(); ++i) { @@ -161,7 +161,7 @@ void AIS_Text::Compute( } } -void AIS_Text::ComputeSelection(const Handle(SelectMgr_Selection)&, const int) +void AIS_Text::ComputeSelection(const OccHandle&, const int) { } diff --git a/src/graphics/ais_text.h b/src/graphics/ais_text.h index 6c5a0f0a..b622a24a 100644 --- a/src/graphics/ais_text.h +++ b/src/graphics/ais_text.h @@ -31,8 +31,8 @@ class AIS_Text : public AIS_InteractiveObject { AIS_Text() = default; AIS_Text(const TCollection_ExtendedString& text, const gp_Pnt& pos); - Handle_Prs3d_TextAspect presentationTextAspect(unsigned i = 0) const; - Handle_Graphic3d_AspectText3d graphicTextAspect(unsigned i = 0) const; + OccHandle presentationTextAspect(unsigned i = 0) const; + OccHandle graphicTextAspect(unsigned i = 0) const; void setDefaultColor(const Quantity_Color& c); void setDefaultFont(const char* fontName); @@ -54,16 +54,16 @@ class AIS_Text : public AIS_InteractiveObject { unsigned textCount() const; void addText(const TCollection_ExtendedString& text, const gp_Pnt& pos); - void ComputeSelection(const Handle(SelectMgr_Selection)& sel, const int mode) override; + void ComputeSelection(const OccHandle& sel, const int mode) override; protected: void Compute( - const Handle(PrsMgr_PresentationManager)& pm, - const Handle(Prs3d_Presentation)& pres, + const OccHandle& pm, + const OccHandle& pres, const int mode) override; #if OCC_VERSION_HEX < OCC_VERSION_CHECK(7, 5, 0) - void Compute(const Handle(Prs3d_Projector)&, const Handle(Prs3d_Presentation)&) override {} + void Compute(const OccHandle&, const OccHandle&) override {} #endif private: @@ -73,7 +73,7 @@ class AIS_Text : public AIS_InteractiveObject { const char* m_font = nullptr; gp_Pnt m_position; TCollection_ExtendedString m_text; - Handle_Prs3d_TextAspect m_aspect; + OccHandle m_aspect; }; const char* m_defaultFont = "Courrier"; diff --git a/src/graphics/graphics_create_driver.cpp b/src/graphics/graphics_create_driver.cpp index 119465b7..17a29682 100644 --- a/src/graphics/graphics_create_driver.cpp +++ b/src/graphics/graphics_create_driver.cpp @@ -10,13 +10,15 @@ // #defines constants like "None" which causes name clash with GuiDocument::ViewTrihedronMode::None // -- +#include "../base/occ_handle.h" + #include #include #include namespace Mayo { -using FunctionCreateGraphicsDriver = std::function; +using FunctionCreateGraphicsDriver = std::function()>; static FunctionCreateGraphicsDriver& getFunctionCreateGraphicsDriver() { @@ -31,7 +33,7 @@ void setFunctionCreateGraphicsDriver(FunctionCreateGraphicsDriver fn) getFunctionCreateGraphicsDriver() = std::move(fn); } -Handle_Graphic3d_GraphicDriver graphicsCreateDriver() +OccHandle graphicsCreateDriver() { const auto& fn = getFunctionCreateGraphicsDriver(); if (fn) diff --git a/src/graphics/graphics_create_virtual_window.cpp b/src/graphics/graphics_create_virtual_window.cpp index 7b1551a5..39e4a7e6 100644 --- a/src/graphics/graphics_create_virtual_window.cpp +++ b/src/graphics/graphics_create_virtual_window.cpp @@ -16,6 +16,8 @@ # include #endif +#include "../base/occ_handle.h" + #include #include #if defined(MAYO_OS_WINDOWS) @@ -31,12 +33,12 @@ namespace Mayo { -Handle_Aspect_Window graphicsCreateVirtualWindow(const Handle_Graphic3d_GraphicDriver& gfxDriver, int wndWidth, int wndHeight) +OccHandle graphicsCreateVirtualWindow(const OccHandle& gfxDriver, int wndWidth, int wndHeight) { #if defined(MAYO_OS_WINDOWS) MAYO_UNUSED(gfxDriver); // Create a "virtual" WNT window being a pure WNT window redefined to be never shown - static Handle_WNT_WClass wClass; + static OccHandle wClass; if (wClass.IsNull()) { auto cursor = LoadCursor(NULL, IDC_ARROW); wClass = new WNT_WClass("GW3D_Class", nullptr, CS_VREDRAW | CS_HREDRAW, 0, 0, cursor); diff --git a/src/graphics/graphics_mesh_data_source.cpp b/src/graphics/graphics_mesh_data_source.cpp index 607f7155..74b294fc 100644 --- a/src/graphics/graphics_mesh_data_source.cpp +++ b/src/graphics/graphics_mesh_data_source.cpp @@ -16,7 +16,7 @@ namespace Mayo { -GraphicsMeshDataSource::GraphicsMeshDataSource(const Handle_Poly_Triangulation& mesh) +GraphicsMeshDataSource::GraphicsMeshDataSource(const OccHandle& mesh) : m_mesh(mesh) { if (!m_mesh.IsNull()) { @@ -67,7 +67,8 @@ bool GraphicsMeshDataSource::GetGeom( const bool IsElement, TColStd_Array1OfReal& Coords, int& NbNodes, - MeshVS_EntityType& Type) const + MeshVS_EntityType& Type + ) const { if (m_mesh.IsNull()) return false; diff --git a/src/graphics/graphics_mesh_data_source.h b/src/graphics/graphics_mesh_data_source.h index de56223a..93edaa49 100644 --- a/src/graphics/graphics_mesh_data_source.h +++ b/src/graphics/graphics_mesh_data_source.h @@ -10,6 +10,8 @@ // -- Basically the same as XSDRAWSTLVRML_DataSource but it allows to be free of TKXSDRAW // -- +#include "../base/occ_handle.h" + #include #include #include @@ -21,7 +23,7 @@ namespace Mayo { class GraphicsMeshDataSource : public MeshVS_DataSource { public: - GraphicsMeshDataSource(const Handle_Poly_Triangulation& mesh); + GraphicsMeshDataSource(const OccHandle& mesh); bool GetGeom(const int ID, const bool IsElement, TColStd_Array1OfReal& Coords, int& NbNodes, MeshVS_EntityType& Type) const override; bool GetGeomType(const int ID, const bool IsElement, MeshVS_EntityType& Type) const override; @@ -32,12 +34,12 @@ class GraphicsMeshDataSource : public MeshVS_DataSource { bool GetNormal(const int Id, const int Max, double& nx, double& ny, double& nz) const override; private: - Handle_Poly_Triangulation m_mesh; + OccHandle m_mesh; TColStd_PackedMapOfInteger m_nodes; TColStd_PackedMapOfInteger m_elements; - Handle_TColStd_HArray2OfInteger m_elemNodes; - Handle_TColStd_HArray2OfReal m_nodeCoords; - Handle_TColStd_HArray2OfReal m_elemNormals; + OccHandle m_elemNodes; + OccHandle m_nodeCoords; + OccHandle m_elemNormals; }; } // namespace Mayo diff --git a/src/graphics/graphics_mesh_object_driver.cpp b/src/graphics/graphics_mesh_object_driver.cpp index 08b0e496..27ef4c4c 100644 --- a/src/graphics/graphics_mesh_object_driver.cpp +++ b/src/graphics/graphics_mesh_object_driver.cpp @@ -48,13 +48,13 @@ GraphicsMeshObjectDriver::Support GraphicsMeshObjectDriver::supportStatus(const GraphicsObjectPtr GraphicsMeshObjectDriver::createObject(const TDF_Label& label) const { - Handle_Poly_Triangulation polyTri; + OccHandle polyTri; Span spanNodeColor; //const TopLoc_Location* ptrLocationPolyTri = nullptr; if (XCaf::isShape(label)) { const TopoDS_Shape shape = XCaf::shape(label); if (shape.ShapeType() == TopAbs_FACE) { - auto tface = Handle_BRep_TFace::DownCast(shape.TShape()); + auto tface = OccHandle::DownCast(shape.TShape()); if (tface) { polyTri = tface->Triangulation(); //ptrLocationPolyTri = &shape.Location(); @@ -67,7 +67,7 @@ GraphicsObjectPtr GraphicsMeshObjectDriver::createObject(const TDF_Label& label) } if (polyTri) { - Handle_MeshVS_Mesh object = new MeshVS_Mesh; + OccHandle object = new MeshVS_Mesh; object->SetDataSource(new GraphicsMeshDataSource(polyTri)); // meshVisu->AddBuilder(..., false); -> No selection if (!spanNodeColor.empty()) { @@ -124,7 +124,7 @@ class GraphicsMeshObjectDriver::ObjectProperties : public PropertyGroupSignals { int countShowEdges = 0; int countShowNodes = 0; for (const GraphicsObjectPtr& object : spanObject) { - auto meshVisu = Handle_MeshVS_Mesh::DownCast(object); + auto meshVisu = OccHandle::DownCast(object); // Color Quantity_Color color; meshVisu->GetDrawer()->GetColor(MeshVS_DA_InteriorColor, color); @@ -167,7 +167,7 @@ class GraphicsMeshObjectDriver::ObjectProperties : public PropertyGroupSignals { if (prop == &m_propertyShowEdges) { if (m_propertyShowEdges.value() != CheckState::Partially) { - for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { + for (const OccHandle& meshVisu : m_vecMeshVisu) { meshVisu->GetDrawer()->SetBoolean(MeshVS_DA_ShowEdges, m_propertyShowEdges.value() == CheckState::On); fnRedisplay(meshVisu); } @@ -175,20 +175,20 @@ class GraphicsMeshObjectDriver::ObjectProperties : public PropertyGroupSignals { } else if (prop == &m_propertyShowNodes) { if (m_propertyShowNodes.value() != CheckState::Partially) { - for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { + for (const OccHandle& meshVisu : m_vecMeshVisu) { meshVisu->GetDrawer()->SetBoolean(MeshVS_DA_DisplayNodes, m_propertyShowNodes.value() == CheckState::On); fnRedisplay(meshVisu); } } } else if (prop == &m_propertyColor) { - for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { + for (const OccHandle& meshVisu : m_vecMeshVisu) { meshVisu->GetDrawer()->SetColor(MeshVS_DA_InteriorColor, m_propertyColor); fnRedisplay(meshVisu); } } else if (prop == &m_propertyEdgeColor) { - for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { + for (const OccHandle& meshVisu : m_vecMeshVisu) { meshVisu->GetDrawer()->SetColor(MeshVS_DA_EdgeColor, m_propertyEdgeColor); fnRedisplay(meshVisu); } @@ -197,7 +197,7 @@ class GraphicsMeshObjectDriver::ObjectProperties : public PropertyGroupSignals { PropertyGroupSignals::onPropertyChanged(prop); } - std::vector m_vecMeshVisu; + std::vector> m_vecMeshVisu; PropertyOccColor m_propertyColor{ this, GraphicsMeshObjectDriverI18N::textId("color") }; PropertyOccColor m_propertyEdgeColor{ this, GraphicsMeshObjectDriverI18N::textId("edgeColor") }; PropertyCheckState m_propertyShowEdges{ this, GraphicsMeshObjectDriverI18N::textId("showEdges") }; diff --git a/src/graphics/graphics_mesh_object_driver.h b/src/graphics/graphics_mesh_object_driver.h index 175b95b3..9beef59d 100644 --- a/src/graphics/graphics_mesh_object_driver.h +++ b/src/graphics/graphics_mesh_object_driver.h @@ -12,7 +12,7 @@ namespace Mayo { class GraphicsMeshObjectDriver; DEFINE_STANDARD_HANDLE(GraphicsMeshObjectDriver, GraphicsObjectDriver) -using GraphicsMeshObjectDriverPtr = Handle(GraphicsMeshObjectDriver); +using GraphicsMeshObjectDriverPtr = OccHandle; // Provides creation and configuration of graphics objects for meshes(triangulations) class GraphicsMeshObjectDriver : public GraphicsObjectDriver { diff --git a/src/graphics/graphics_object_driver.h b/src/graphics/graphics_object_driver.h index d85dff39..60ef5145 100644 --- a/src/graphics/graphics_object_driver.h +++ b/src/graphics/graphics_object_driver.h @@ -20,7 +20,7 @@ namespace Mayo { class GraphicsObjectDriver; DEFINE_STANDARD_HANDLE(GraphicsObjectDriver, Standard_Transient) -using GraphicsObjectDriverPtr = Handle(GraphicsObjectDriver); +using GraphicsObjectDriverPtr = OccHandle; // Provides creation and configuration of graphics objects of a specific type // Each graphics object "knows" the driver which created it: use function GraphicsObjectDriver::get() diff --git a/src/graphics/graphics_object_ptr.h b/src/graphics/graphics_object_ptr.h index c61a8658..65052fe0 100644 --- a/src/graphics/graphics_object_ptr.h +++ b/src/graphics/graphics_object_ptr.h @@ -6,11 +6,12 @@ #pragma once +#include "../base/occ_handle.h" #include namespace Mayo { -using GraphicsObjectPtr = Handle(AIS_InteractiveObject); +using GraphicsObjectPtr = OccHandle; using GraphicsObjectSelectionMode = int; } // namespace Mayo diff --git a/src/graphics/graphics_owner_ptr.h b/src/graphics/graphics_owner_ptr.h index 81fc98d5..3e747b9a 100644 --- a/src/graphics/graphics_owner_ptr.h +++ b/src/graphics/graphics_owner_ptr.h @@ -6,10 +6,11 @@ #pragma once +#include "../base/occ_handle.h" #include namespace Mayo { -using GraphicsOwnerPtr = Handle(SelectMgr_EntityOwner); +using GraphicsOwnerPtr = OccHandle; } // namespace Mayo diff --git a/src/graphics/graphics_point_cloud_object_driver.h b/src/graphics/graphics_point_cloud_object_driver.h index 86aafac6..bb99a0ee 100644 --- a/src/graphics/graphics_point_cloud_object_driver.h +++ b/src/graphics/graphics_point_cloud_object_driver.h @@ -13,7 +13,7 @@ namespace Mayo { // Pre-declarations class GraphicsPointCloudObjectDriver; DEFINE_STANDARD_HANDLE(GraphicsPointCloudObjectDriver, GraphicsObjectDriver) -using GraphicsPointCloudObjectDriverPtr = Handle(GraphicsPointCloudObjectDriver); +using GraphicsPointCloudObjectDriverPtr = OccHandle; // Provides creation and configuration of graphics objects for point clouds class GraphicsPointCloudObjectDriver : public GraphicsObjectDriver { diff --git a/src/graphics/graphics_scene.cpp b/src/graphics/graphics_scene.cpp index ccede57d..fc28bfd8 100644 --- a/src/graphics/graphics_scene.cpp +++ b/src/graphics/graphics_scene.cpp @@ -19,13 +19,13 @@ namespace Mayo { // Defined in graphics_create_driver.cpp -Handle_Graphic3d_GraphicDriver graphicsCreateDriver(); +OccHandle graphicsCreateDriver(); namespace Internal { -static Handle_V3d_Viewer createOccViewer() +static OccHandle createOccViewer() { - Handle_V3d_Viewer viewer = new V3d_Viewer(graphicsCreateDriver()); + OccHandle viewer = new V3d_Viewer(graphicsCreateDriver()); viewer->SetDefaultViewSize(1000.); viewer->SetDefaultViewProj(V3d_XposYnegZpos); viewer->SetComputedMode(true); @@ -48,7 +48,7 @@ static Handle_V3d_Viewer createOccViewer() viewer->SetLightOn(); #if 0 - for (const Handle(Graphic3d_CLight)& light : viewer->DefinedLights()) { + for (const OccHandle& light : viewer->DefinedLights()) { if (light->Name() == "amblight" || light->Type() == Graphic3d_TypeOfLightSource_Ambient) { light->SetIntensity(1.f); } @@ -70,7 +70,7 @@ namespace { class InteractiveContext : public AIS_InteractiveContext { DEFINE_STANDARD_RTTI_INLINE(InteractiveContext, AIS_InteractiveContext) public: - InteractiveContext(const Handle_V3d_Viewer& viewer) + InteractiveContext(const OccHandle& viewer) : AIS_InteractiveContext(viewer) {} @@ -92,7 +92,7 @@ DEFINE_STANDARD_HANDLE(InteractiveContext, AIS_InteractiveContext) class GraphicsScene::Private { public: - opencascade::handle m_aisContext; + OccHandle m_aisContext; std::unordered_set m_setClipPlaneSensitive; bool m_isRedrawBlocked = false; SelectionMode m_selectionMode = SelectionMode::Single; @@ -114,17 +114,17 @@ GraphicsScene::~GraphicsScene() delete d; } -opencascade::handle GraphicsScene::createV3dView() +OccHandle GraphicsScene::createV3dView() { return this->v3dViewer()->CreateView(); } -const opencascade::handle& GraphicsScene::v3dViewer() const +const OccHandle& GraphicsScene::v3dViewer() const { return d->m_aisContext->CurrentViewer(); } -const opencascade::handle& GraphicsScene::mainSelector() const +const OccHandle& GraphicsScene::mainSelector() const { return d->m_aisContext->MainSelector(); } @@ -134,12 +134,12 @@ bool GraphicsScene::hiddenLineDrawingOn() const return d->m_aisContext->DrawHiddenLine(); } -const opencascade::handle& GraphicsScene::drawerDefault() const +const OccHandle& GraphicsScene::drawerDefault() const { return d->m_aisContext->DefaultDrawer(); } -const opencascade::handle& GraphicsScene::drawerHighlight(Prs3d_TypeOfHighlight style) const +const OccHandle& GraphicsScene::drawerHighlight(Prs3d_TypeOfHighlight style) const { return d->m_aisContext->HighlightStyle(style); } @@ -304,7 +304,7 @@ AIS_InteractiveContext* GraphicsScene::aisContextPtr() const void GraphicsScene::toggleOwnerSelection(const GraphicsOwnerPtr& gfxOwner) { auto gfxObject = GraphicsObjectPtr::DownCast( - gfxOwner ? gfxOwner->Selectable() : Handle_SelectMgr_SelectableObject() + gfxOwner ? gfxOwner->Selectable() : OccHandle() ); if (GraphicsUtils::AisObject_isVisible(gfxObject)) d->m_aisContext->AddOrRemoveSelected(gfxOwner, false); diff --git a/src/graphics/graphics_scene.h b/src/graphics/graphics_scene.h index b15a812f..295acdcc 100644 --- a/src/graphics/graphics_scene.h +++ b/src/graphics/graphics_scene.h @@ -158,7 +158,7 @@ void GraphicsScene::foreachActiveSelectionMode(const GraphicsObjectPtr& object, template void GraphicsScene::foreachOwner(const GraphicsObjectPtr& object, int selectionMode, Function fn) const { - opencascade::handle mapEntityOwner; + OccHandle mapEntityOwner; this->aisContextPtr()->EntityOwners(mapEntityOwner, object, selectionMode); for (auto it = mapEntityOwner->cbegin(); it != mapEntityOwner->cend(); ++it) fn(*it); diff --git a/src/graphics/graphics_shape_object_driver.cpp b/src/graphics/graphics_shape_object_driver.cpp index e63267a9..41e8d9c0 100644 --- a/src/graphics/graphics_shape_object_driver.cpp +++ b/src/graphics/graphics_shape_object_driver.cpp @@ -95,7 +95,7 @@ void GraphicsShapeObjectDriver::applyDisplayMode(GraphicsObjectPtr object, Enume if (object->Attributes()->FaceBoundaryDraw() != showFaceBounds) { object->Attributes()->SetFaceBoundaryDraw(showFaceBounds); - auto aisLink = Handle_AIS_ConnectedInteractive::DownCast(object); + auto aisLink = OccHandle::DownCast(object); if (aisLink && aisLink->HasConnection()) { aisLink->ConnectedTo()->Attributes()->SetFaceBoundaryDraw(showFaceBounds); aisLink->ConnectedTo()->Redisplay(true); diff --git a/src/graphics/graphics_shape_object_driver.h b/src/graphics/graphics_shape_object_driver.h index 6e56acb5..62eeb2d0 100644 --- a/src/graphics/graphics_shape_object_driver.h +++ b/src/graphics/graphics_shape_object_driver.h @@ -12,7 +12,7 @@ namespace Mayo { class GraphicsShapeObjectDriver; DEFINE_STANDARD_HANDLE(GraphicsShapeObjectDriver, GraphicsObjectDriver) -using GraphicsShapeObjectDriverPtr = Handle(GraphicsShapeObjectDriver); +using GraphicsShapeObjectDriverPtr = OccHandle; // Provides creation and configuration of graphics objects for BRep shapes class GraphicsShapeObjectDriver : public GraphicsObjectDriver { diff --git a/src/graphics/graphics_utils.cpp b/src/graphics/graphics_utils.cpp index 4064b0f7..99022d16 100644 --- a/src/graphics/graphics_utils.cpp +++ b/src/graphics/graphics_utils.cpp @@ -189,7 +189,7 @@ Bnd_Box GraphicsUtils::AisObject_boundingBox(const OccHandlePresentations()) { if (pres.Mode() == object->DisplayMode()) { - const Handle_Prs3d_Presentation& pres3d = pres.Presentation()->Presentation(); + const OccHandle& pres3d = pres.Presentation()->Presentation(); if (!pres3d->CStructure()->BoundingBox().IsValid()) pres3d->CalculateBoundBox(); } diff --git a/src/graphics/graphics_utils.h b/src/graphics/graphics_utils.h index 69884bcb..c9a86176 100644 --- a/src/graphics/graphics_utils.h +++ b/src/graphics/graphics_utils.h @@ -45,13 +45,13 @@ struct GraphicsUtils { static void V3dViewer_setGridColors(const OccHandle& viewer, const AspectGridColors& colors); static void AisContext_eraseObject( - const OccHandle& context, - const OccHandle& object + const OccHandle& context, + const OccHandle& object ); static void AisContext_setObjectVisible( - const OccHandle& context, - const OccHandle& object, - bool on + const OccHandle& context, + const OccHandle& object, + bool on ); static AIS_InteractiveContext* AisObject_contextPtr(const OccHandle& object); @@ -64,16 +64,17 @@ struct GraphicsUtils { static OccHandle AspectDisplayConnection_create(); static void Gfx3dClipPlane_setCappingHatch( - const OccHandle& plane, Aspect_HatchStyle hatch + const OccHandle& plane, Aspect_HatchStyle hatch ); static void Gfx3dClipPlane_setNormal( - const OccHandle& plane, const gp_Dir& n + const OccHandle& plane, const gp_Dir& n ); static void Gfx3dClipPlane_setPosition( - const OccHandle& plane, double pos + const OccHandle& plane, double pos ); static bool ImagePixmap_flipY(Image_PixMap& pixmap); + }; } // namespace Mayo diff --git a/src/gui/gui_document.cpp b/src/gui/gui_document.cpp index 5e07c091..66c0a37b 100644 --- a/src/gui/gui_document.cpp +++ b/src/gui/gui_document.cpp @@ -32,10 +32,10 @@ namespace Mayo { namespace Internal { -static Handle_AIS_Trihedron createOriginTrihedron() +static OccHandle createOriginTrihedron() { - Handle_Geom_Axis2Placement axis = new Geom_Axis2Placement(gp::XOY()); - Handle_AIS_Trihedron aisTrihedron = new AIS_Trihedron(axis); + OccHandle axis = new Geom_Axis2Placement(gp::XOY()); + OccHandle aisTrihedron = new AIS_Trihedron(axis); aisTrihedron->SetDatumDisplayMode(Prs3d_DM_WireFrame); aisTrihedron->SetDrawArrows(false); aisTrihedron->Attributes()->DatumAspect()->LineAspect(Prs3d_DP_XAxis)->SetWidth(2.5); @@ -126,7 +126,7 @@ void GuiDocument::setDevicePixelRatio(double ratio) } case ViewTrihedronMode::AisViewCube: { #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) - auto viewCube = Handle(AIS_ViewCube)::DownCast(m_aisViewCube); + auto viewCube = OccHandle::DownCast(m_aisViewCube); if (viewCube) { viewCube->SetSize(55 * m_devicePixelRatio, true/*adaptOtherParams*/); viewCube->SetFontHeight(12 * m_devicePixelRatio); @@ -360,7 +360,7 @@ bool GuiDocument::processAction(const GraphicsOwnerPtr& gfxOwner) return false; #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) - auto viewCubeOwner = Handle(AIS_ViewCubeOwner)::DownCast(gfxOwner); + auto viewCubeOwner = OccHandle::DownCast(gfxOwner); if (viewCubeOwner) { this->setViewCameraOrientation(viewCubeOwner->MainOrientation()); return true; @@ -372,7 +372,7 @@ bool GuiDocument::processAction(const GraphicsOwnerPtr& gfxOwner) void GuiDocument::setViewCameraOrientation(V3d_TypeOfOrientation projection) { - this->runViewCameraAnimation([=](Handle_V3d_View view) { + this->runViewCameraAnimation([=](OccHandle view) { view->SetProj(projection); GraphicsUtils::V3dView_fitAll(view); }); @@ -426,7 +426,7 @@ void GuiDocument::setViewTrihedronMode(ViewTrihedronMode mode) ); m_gfxScene.addObject(aisViewCube); //aisViewCube->Attributes()->DatumAspect()->LineAspect(Prs3d_DP_XAxis)->SetColor(Quantity_NOC_RED2); - const Handle_Prs3d_DatumAspect& datumAspect = aisViewCube->Attributes()->DatumAspect(); + const OccHandle& datumAspect = aisViewCube->Attributes()->DatumAspect(); datumAspect->ShadingAspect(Prs3d_DP_XAxis)->SetColor(Quantity_NOC_RED2); datumAspect->ShadingAspect(Prs3d_DP_YAxis)->SetColor(Quantity_NOC_GREEN2); datumAspect->ShadingAspect(Prs3d_DP_ZAxis)->SetColor(Quantity_NOC_BLUE2); @@ -475,7 +475,7 @@ int GuiDocument::aisViewCubeBoundingSize() const return 0; #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) - auto hnd = opencascade::handle::DownCast(m_aisViewCube); + auto hnd = OccHandle::DownCast(m_aisViewCube); auto size = 2 * (hnd->Size() + hnd->BoxFacetExtension() @@ -495,7 +495,7 @@ int GuiDocument::aisViewCubeBoundingSize() const bool GuiDocument::isAisViewCubeObject([[maybe_unused]] const GraphicsObjectPtr& gfxObject) { #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) - return !opencascade::handle::DownCast(gfxObject).IsNull(); + return !OccHandle::DownCast(gfxObject).IsNull(); #else return false; #endif @@ -543,7 +543,7 @@ void GuiDocument::onGraphicsSelectionChanged() std::vector vecSelected; m_gfxScene.foreachSelectedOwner([&](const GraphicsOwnerPtr& gfxOwner) { auto gfxObject = GraphicsObjectPtr::DownCast( - gfxOwner ? gfxOwner->Selectable() : Handle_SelectMgr_SelectableObject() + gfxOwner ? gfxOwner->Selectable() : OccHandle() ); const TreeNodeId nodeId = this->nodeFromGraphicsObject(gfxObject); if (nodeId != 0) { diff --git a/src/gui/gui_document.h b/src/gui/gui_document.h index 17b3f60a..9fa8e8c5 100644 --- a/src/gui/gui_document.h +++ b/src/gui/gui_document.h @@ -43,7 +43,7 @@ class GuiDocument { GuiApplication* guiApplication() const { return m_guiApp; } - const Handle_V3d_View& v3dView() const { return m_v3dView; } + const OccHandle& v3dView() const { return m_v3dView; } GraphicsScene* graphicsScene() { return &m_gfxScene; } GraphicsViewPtr graphicsView() { return GraphicsViewPtr{ &m_gfxScene, m_v3dView }; } const Bnd_Box& graphicsBoundingBox() const { return m_gfxBoundingBox; } @@ -152,14 +152,14 @@ class GuiDocument { GuiApplication* m_guiApp = nullptr; DocumentPtr m_document; GraphicsScene m_gfxScene; - Handle_V3d_View m_v3dView; - Handle_AIS_InteractiveObject m_aisOriginTrihedron; + OccHandle m_v3dView; + OccHandle m_aisOriginTrihedron; double m_devicePixelRatio = 1.; V3dViewCameraAnimation* m_cameraAnimation = nullptr; ViewTrihedronMode m_viewTrihedronMode = ViewTrihedronMode::None; Aspect_TypeOfTriedronPosition m_viewTrihedronCorner = Aspect_TOTP_LEFT_UPPER; - Handle_AIS_InteractiveObject m_aisViewCube; + OccHandle m_aisViewCube; std::vector m_vecGraphicsEntity; Bnd_Box m_gfxBoundingBox; diff --git a/src/gui/v3d_view_camera_animation.cpp b/src/gui/v3d_view_camera_animation.cpp index 45c373d5..1d8edc49 100644 --- a/src/gui/v3d_view_camera_animation.cpp +++ b/src/gui/v3d_view_camera_animation.cpp @@ -25,7 +25,7 @@ void V3dViewCameraAnimation::setBackend(std::unique_ptr anim) } } -void V3dViewCameraAnimation::setView(const Handle_V3d_View& view) +void V3dViewCameraAnimation::setView(const OccHandle& view) { if (this->isRunning()) this->stop(); @@ -57,12 +57,12 @@ void V3dViewCameraAnimation::stop() m_backend->stop(); } -void V3dViewCameraAnimation::setCameraStart(const Handle_Graphic3d_Camera& camera) +void V3dViewCameraAnimation::setCameraStart(const OccHandle& camera) { m_cameraStart->Copy(camera); } -void V3dViewCameraAnimation::setCameraEnd(const Handle_Graphic3d_Camera& camera) +void V3dViewCameraAnimation::setCameraEnd(const OccHandle& camera) { m_cameraEnd->Copy(camera); } @@ -96,7 +96,7 @@ void V3dViewCameraAnimation::updateCurrentTime(QuantityTime currTime) const double t = m_backend ? m_backend->valueForProgress(currTime / m_duration) : 0.; const bool prevImmediateUpdate = m_view->SetImmediateUpdate(false); const Graphic3d_CameraLerp cameraLerp(m_cameraStart, m_cameraEnd); - Handle_Graphic3d_Camera camera = m_view->Camera(); + OccHandle camera = m_view->Camera(); cameraLerp.Interpolate(t, camera); m_view->SetCamera(camera); m_view->ZFitAll(); diff --git a/src/gui/v3d_view_camera_animation.h b/src/gui/v3d_view_camera_animation.h index 0b300664..655f6371 100644 --- a/src/gui/v3d_view_camera_animation.h +++ b/src/gui/v3d_view_camera_animation.h @@ -6,6 +6,7 @@ #pragma once +#include "../base/occ_handle.h" #include "../base/quantity.h" #include @@ -29,7 +30,7 @@ class IAnimationBackend { // Provides animation control for 3D view camera class V3dViewCameraAnimation { public: - using ViewFunction = std::function; + using ViewFunction = std::function&)>; V3dViewCameraAnimation(); ~V3dViewCameraAnimation() = default; @@ -37,8 +38,8 @@ class V3dViewCameraAnimation { bool hasBackend() const { return m_backend.get() != nullptr; } void setBackend(std::unique_ptr anim); - const Handle_V3d_View& view() const { return m_view; } - void setView(const Handle_V3d_View& view); + const OccHandle& view() const { return m_view; } + void setView(const OccHandle& view); QuantityTime duration() const { return m_duration; } void setDuration(QuantityTime t); @@ -47,8 +48,8 @@ class V3dViewCameraAnimation { void start(); void stop(); - void setCameraStart(const Handle_Graphic3d_Camera& camera); - void setCameraEnd(const Handle_Graphic3d_Camera& camera); + void setCameraStart(const OccHandle& camera); + void setCameraEnd(const OccHandle& camera); void configureCameraChange(const ViewFunction& fnViewChange); void setRenderFunction(ViewFunction fnViewRender); @@ -57,11 +58,11 @@ class V3dViewCameraAnimation { void updateCurrentTime(QuantityTime currTime); std::unique_ptr m_backend; - Handle_V3d_View m_view; - Handle_Graphic3d_Camera m_cameraStart; - Handle_Graphic3d_Camera m_cameraEnd; + OccHandle m_view; + OccHandle m_cameraStart; + OccHandle m_cameraEnd; QuantityTime m_duration = 1 * Quantity_Second; - std::function m_fnViewRender; + std::function&)> m_fnViewRender; }; } // namespace Mayo diff --git a/src/gui/v3d_view_controller.cpp b/src/gui/v3d_view_controller.cpp index 16fdbbae..1a2af3cc 100644 --- a/src/gui/v3d_view_controller.cpp +++ b/src/gui/v3d_view_controller.cpp @@ -13,7 +13,7 @@ namespace Mayo { -V3dViewController::V3dViewController(const Handle_V3d_View& view) +V3dViewController::V3dViewController(const OccHandle& view) : m_view(view) { } diff --git a/src/gui/v3d_view_controller.h b/src/gui/v3d_view_controller.h index f89a636f..115e8a97 100644 --- a/src/gui/v3d_view_controller.h +++ b/src/gui/v3d_view_controller.h @@ -6,6 +6,7 @@ #pragma once +#include "../base/occ_handle.h" #include "../base/quantity.h" #include "../base/signal.h" #include "gui_vkey_mouse.h" @@ -33,7 +34,7 @@ class V3dViewController { virtual void setVisible(bool on) = 0; }; - V3dViewController(const Handle_V3d_View& view); + V3dViewController(const OccHandle& view); virtual ~V3dViewController() = default; DynamicAction currentDynamicAction() const; @@ -90,11 +91,11 @@ class V3dViewController { virtual void redrawView(); private: - Handle_V3d_View m_view; + OccHandle m_view; DynamicAction m_dynamicAction = DynamicAction::None; std::unique_ptr m_rubberBand; double m_instantZoomFactor = 5.; - Handle_Graphic3d_Camera m_cameraBackup; + OccHandle m_cameraBackup; Position m_posRubberBandStart = {}; }; diff --git a/src/io_dxf/io_dxf.cpp b/src/io_dxf/io_dxf.cpp index b602f4d4..be17feff 100644 --- a/src/io_dxf/io_dxf.cpp +++ b/src/io_dxf/io_dxf.cpp @@ -80,10 +80,10 @@ const Enumeration& systemFontNames() static Enumeration fontNames; static TColStd_SequenceOfHAsciiString seqFontName; if (fontNames.empty()) { - Handle_Font_FontMgr fontMgr = Font_FontMgr::GetInstance(); + OccHandle fontMgr = Font_FontMgr::GetInstance(); fontMgr->GetAvailableFontsNames(seqFontName); int i = 0; - for (const Handle_TCollection_HAsciiString& fontName : seqFontName) + for (const OccHandle& fontName : seqFontName) fontNames.addItem(i++, { {}, to_stdStringView(fontName->String()) }); } @@ -137,8 +137,8 @@ class DxfReader::Internal : public CDxfRead { void ReportError(const std::string& msg) override; void AddGraphics() const override; - static Handle_Geom_BSplineCurve createSplineFromPolesAndKnots(const Dxf_SPLINE& spline); - static Handle_Geom_BSplineCurve createInterpolationSpline(const Dxf_SPLINE& spline); + static OccHandle createSplineFromPolesAndKnots(const Dxf_SPLINE& spline); + static OccHandle createInterpolationSpline(const Dxf_SPLINE& spline); gp_Pnt toPnt(const DxfCoords& coords) const; void addShape(const TopoDS_Shape& shape); @@ -190,9 +190,9 @@ bool DxfReader::readFile(const FilePath& filepath, TaskProgress* progress) TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) { TDF_LabelSequence seqLabel; - Handle_XCAFDoc_ShapeTool shapeTool = doc->xcaf().shapeTool(); - Handle_XCAFDoc_ColorTool colorTool = doc->xcaf().colorTool(); - Handle_XCAFDoc_LayerTool layerTool = doc->xcaf().layerTool(); + OccHandle shapeTool = doc->xcaf().shapeTool(); + OccHandle colorTool = doc->xcaf().colorTool(); + OccHandle layerTool = doc->xcaf().layerTool(); std::unordered_map mapLayerNameLabel; std::unordered_map mapAciColorLabel; @@ -694,7 +694,7 @@ void DxfReader::Internal::OnReadSpline(const Dxf_SPLINE& spline) { // https://documentation.help/AutoCAD-DXF/WS1a9193826455f5ff18cb41610ec0a2e719-79e1.htm try { - Handle_Geom_BSplineCurve geom; + OccHandle geom; if (!spline.controlPoints.empty()) geom = createSplineFromPolesAndKnots(spline); else if (!spline.fitPoints.empty()) @@ -887,7 +887,7 @@ TopoDS_Face DxfReader::Internal::makeFace(const Dxf_QuadBase& quad) const } // Excerpted from FreeCad/src/Mod/Import/App/ImpExpDxf -Handle_Geom_BSplineCurve DxfReader::Internal::createSplineFromPolesAndKnots(const Dxf_SPLINE& spline) +OccHandle DxfReader::Internal::createSplineFromPolesAndKnots(const Dxf_SPLINE& spline) { if (spline.weights.size() > spline.controlPoints.size()) return {}; @@ -951,7 +951,7 @@ Handle_Geom_BSplineCurve DxfReader::Internal::createSplineFromPolesAndKnots(cons } // Excerpted from FreeCad/src/Mod/Import/App/ImpExpDxf -Handle_Geom_BSplineCurve DxfReader::Internal::createInterpolationSpline(const Dxf_SPLINE& spline) +OccHandle DxfReader::Internal::createInterpolationSpline(const Dxf_SPLINE& spline) { const auto iNumPoints = CppUtils::safeStaticCast(spline.fitPoints.size()); diff --git a/src/io_gmio/io_gmio_amf_writer.cpp b/src/io_gmio/io_gmio_amf_writer.cpp index 4222c816..9bd77fb7 100644 --- a/src/io_gmio/io_gmio_amf_writer.cpp +++ b/src/io_gmio/io_gmio_amf_writer.cpp @@ -319,7 +319,7 @@ int GmioAmfWriter::createObject(const TDF_Label& labelShape) // Object meshes const int meshCount = int(m_vecMesh.size()); - auto fnAddMesh = [&](const Handle_Poly_Triangulation& polyTri, const TopLoc_Location& loc) { + auto fnAddMesh = [&](const OccHandle& polyTri, const TopLoc_Location& loc) { if (!polyTri.IsNull()) { Mesh mesh; mesh.id = int(m_vecMesh.size()); diff --git a/src/io_gmio/io_gmio_amf_writer.h b/src/io_gmio/io_gmio_amf_writer.h index 39adcc54..7042e5d4 100644 --- a/src/io_gmio/io_gmio_amf_writer.h +++ b/src/io_gmio/io_gmio_amf_writer.h @@ -112,7 +112,7 @@ class GmioAmfWriter : public Writer { struct Mesh { int id = -1; - Handle_Poly_Triangulation triangulation; + OccHandle triangulation; TopLoc_Location location; int materialId = -1; }; diff --git a/src/io_image/io_image.cpp b/src/io_image/io_image.cpp index 88f09b6a..9bd63361 100644 --- a/src/io_image/io_image.cpp +++ b/src/io_image/io_image.cpp @@ -35,7 +35,7 @@ namespace Mayo { // Defined in graphics_create_virtual_window.cpp -Handle_Aspect_Window graphicsCreateVirtualWindow(const Handle_Graphic3d_GraphicDriver&, int , int); +OccHandle graphicsCreateVirtualWindow(const OccHandle&, int , int); namespace IO { @@ -110,7 +110,7 @@ bool ImageWriter::writeFile(const FilePath& filepath, TaskProgress* progress) // Create 3D view GraphicsScene gfxScene; - Handle_V3d_View view = ImageWriter::createV3dView(&gfxScene, m_params); + OccHandle view = ImageWriter::createV3dView(&gfxScene, m_params); const int itemCount = CppUtils::safeStaticCast(m_vecAppItem.size()); // Render application items @@ -134,7 +134,7 @@ bool ImageWriter::writeFile(const FilePath& filepath, TaskProgress* progress) view->Redraw(); GraphicsUtils::V3dView_fitAll(view); - Handle_Image_AlienPixMap pixmap = ImageWriter::createImage(view); + OccHandle pixmap = ImageWriter::createImage(view); if (!pixmap) return false; @@ -159,14 +159,14 @@ void ImageWriter::applyProperties(const PropertyGroup* params) } } -Handle_Image_AlienPixMap ImageWriter::createImage(GuiDocument* guiDoc, const Parameters& params) +OccHandle ImageWriter::createImage(GuiDocument* guiDoc, const Parameters& params) { if (!guiDoc) return {}; const GuiDocument::ViewTrihedronMode onEntryTrihedronMode = guiDoc->viewTrihedronMode(); const bool onEntryOriginTrihedronVisible = guiDoc->isOriginTrihedronVisible(); - Handle_V3d_View view = ImageWriter::createV3dView(guiDoc->graphicsScene(), params); + OccHandle view = ImageWriter::createV3dView(guiDoc->graphicsScene(), params); auto _ = gsl::finally([=]{ guiDoc->graphicsScene()->v3dViewer()->SetViewOff(view); @@ -184,9 +184,9 @@ Handle_Image_AlienPixMap ImageWriter::createImage(GuiDocument* guiDoc, const Par return ImageWriter::createImage(view); } -Handle_Image_AlienPixMap ImageWriter::createImage(Handle_V3d_View view) +OccHandle ImageWriter::createImage(OccHandle view) { - Handle_Image_AlienPixMap pixmap = new Image_AlienPixMap; + OccHandle pixmap = new Image_AlienPixMap; V3d_ImageDumpOptions dumpOptions; dumpOptions.BufferType = Graphic3d_BT_RGB; view->Window()->Size(dumpOptions.Width, dumpOptions.Height); @@ -198,7 +198,7 @@ Handle_Image_AlienPixMap ImageWriter::createImage(Handle_V3d_View view) return pixmap; } -Handle_V3d_View ImageWriter::createV3dView(GraphicsScene* gfxScene, const Parameters& params) +OccHandle ImageWriter::createV3dView(GraphicsScene* gfxScene, const Parameters& params) { auto fnToGfxCamProjection = [](ImageWriter::CameraProjection proj) { switch (proj) { @@ -209,7 +209,7 @@ Handle_V3d_View ImageWriter::createV3dView(GraphicsScene* gfxScene, const Parame }; // Create 3D view - Handle_V3d_View view = gfxScene->createV3dView(); + OccHandle view = gfxScene->createV3dView(); view->ChangeRenderingParams().IsAntialiasingEnabled = true; view->ChangeRenderingParams().NbMsaaSamples = 4; view->SetBackgroundColor(params.backgroundColor); diff --git a/src/io_image/io_image.h b/src/io_image/io_image.h index 66b70076..a2e9ea22 100644 --- a/src/io_image/io_image.h +++ b/src/io_image/io_image.h @@ -59,9 +59,9 @@ class ImageWriter : public Writer { const Parameters& constParameters() const { return m_params; } // Helper - static Handle_Image_AlienPixMap createImage(GuiDocument* guiDoc, const Parameters& params); - static Handle_Image_AlienPixMap createImage(Handle_V3d_View view); - static Handle_V3d_View createV3dView(GraphicsScene* gfxScene, const Parameters& params); + static OccHandle createImage(GuiDocument* guiDoc, const Parameters& params); + static OccHandle createImage(OccHandle view); + static OccHandle createV3dView(GraphicsScene* gfxScene, const Parameters& params); private: class Properties; diff --git a/src/io_occ/io_occ_base_mesh.cpp b/src/io_occ/io_occ_base_mesh.cpp index a4a90592..46f8ff8f 100644 --- a/src/io_occ/io_occ_base_mesh.cpp +++ b/src/io_occ/io_occ_base_mesh.cpp @@ -77,7 +77,7 @@ TDF_LabelSequence OccBaseMeshReader::transfer(DocumentPtr doc, TaskProgress* pro this->applyParameters(); m_reader.SetDocument(doc); const TDF_LabelSequence seqMark = doc->xcaf().topLevelFreeShapes(); - Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); + OccHandle indicator = new OccProgressIndicator(progress); m_reader.Perform(m_filepath.u8string().c_str(), TKernelUtils::start(indicator)); return doc->xcaf().diffTopLevelFreeShapes(seqMark); } diff --git a/src/io_occ/io_occ_brep.cpp b/src/io_occ/io_occ_brep.cpp index f39c7ec3..72630d9e 100644 --- a/src/io_occ/io_occ_brep.cpp +++ b/src/io_occ/io_occ_brep.cpp @@ -28,7 +28,7 @@ bool OccBRepReader::readFile(const FilePath& filepath, TaskProgress* progress) m_shape.Nullify(); m_baseFilename = filepath.stem(); BRep_Builder brepBuilder; - Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); + OccHandle indicator = new OccProgressIndicator(progress); return BRepTools::Read( m_shape, filepath.u8string().c_str(), @@ -41,7 +41,7 @@ TDF_LabelSequence OccBRepReader::transfer(DocumentPtr doc, TaskProgress* /*progr if (m_shape.IsNull()) return {}; - const Handle_XCAFDoc_ShapeTool shapeTool = doc->xcaf().shapeTool(); + const OccHandle shapeTool = doc->xcaf().shapeTool(); const TDF_Label labelShape = shapeTool->NewShape(); shapeTool->SetShape(labelShape, m_shape); TDataStd_Name::Set(labelShape, filepathTo(m_baseFilename)); @@ -79,7 +79,7 @@ bool OccBRepWriter::transfer(Span appItems, TaskProgress* bool OccBRepWriter::writeFile(const FilePath& filepath, TaskProgress* progress) { - Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); + OccHandle indicator = new OccProgressIndicator(progress); return BRepTools::Write(m_shape, filepath.u8string().c_str(), TKernelUtils::start(indicator)); } diff --git a/src/io_occ/io_occ_caf.cpp b/src/io_occ/io_occ_caf.cpp index 269347da..a78d618a 100644 --- a/src/io_occ/io_occ_caf.cpp +++ b/src/io_occ/io_occ_caf.cpp @@ -35,13 +35,13 @@ bool cafGenericReadFile(CafReaderType& reader, const FilePath& filepath, TaskPro template TDF_LabelSequence cafGenericReadTransfer(CafReaderType& reader, DocumentPtr doc, TaskProgress* progress) { - Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); + OccHandle indicator = new OccProgressIndicator(progress); const TDF_LabelSequence seqMark = doc->xcaf().topLevelFreeShapes(); - Handle_TDocStd_Document stdDoc = doc; + OccHandle stdDoc = doc; #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) const bool okTransfer = reader.Transfer(stdDoc, indicator->Start()); #else - Handle_XSControl_WorkSession ws = Private::cafWorkSession(reader); + OccHandle ws = Private::cafWorkSession(reader); ws->MapReader()->SetProgress(indicator); auto _ = gsl::finally([&]{ ws->MapReader()->SetProgress(nullptr); }); const bool okTransfer = reader.Transfer(stdDoc); @@ -53,7 +53,7 @@ TDF_LabelSequence cafGenericReadTransfer(CafReaderType& reader, DocumentPtr doc, template bool cafGenericWriteTransfer(CafWriterType& writer, Span appItems, TaskProgress* progress) { - Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); + OccHandle indicator = new OccProgressIndicator(progress); #if OCC_VERSION_HEX < OCC_VERSION_CHECK(7, 5, 0) Private::cafFinderProcess(writer)->SetProgress(indicator); auto _ = gsl::finally([&]{ Private::cafFinderProcess(writer)->SetProgress(nullptr); }); @@ -87,11 +87,11 @@ std::mutex& cafGlobalMutex() return mutex; } -Handle_XSControl_WorkSession cafWorkSession(const STEPCAFControl_Reader& reader) { +OccHandle cafWorkSession(const STEPCAFControl_Reader& reader) { return reader.Reader().WS(); } -Handle_XSControl_WorkSession cafWorkSession(const IGESCAFControl_Reader& reader) { +OccHandle cafWorkSession(const IGESCAFControl_Reader& reader) { return reader.WS(); } @@ -111,11 +111,11 @@ TDF_LabelSequence cafTransfer(STEPCAFControl_Reader& reader, DocumentPtr doc, Ta return cafGenericReadTransfer(reader, doc, progress); } -Handle_Transfer_FinderProcess cafFinderProcess(const IGESCAFControl_Writer& writer) { +OccHandle cafFinderProcess(const IGESCAFControl_Writer& writer) { return writer.TransferProcess(); } -Handle_Transfer_FinderProcess cafFinderProcess(const STEPCAFControl_Writer& writer) { +OccHandle cafFinderProcess(const STEPCAFControl_Writer& writer) { return writer.Writer().WS()->TransferWriter()->FinderProcess(); } diff --git a/src/io_occ/io_occ_caf.h b/src/io_occ/io_occ_caf.h index 2391de69..a00b4dca 100644 --- a/src/io_occ/io_occ_caf.h +++ b/src/io_occ/io_occ_caf.h @@ -36,11 +36,11 @@ std::mutex& cafGlobalMutex(); #define MayoIO_CafGlobalScopedLock(name) \ [[maybe_unused]] std::lock_guard name(Mayo::IO::Private::cafGlobalMutex()); -Handle_XSControl_WorkSession cafWorkSession(const IGESCAFControl_Reader& reader); -Handle_XSControl_WorkSession cafWorkSession(const STEPCAFControl_Reader& reader); +OccHandle cafWorkSession(const IGESCAFControl_Reader& reader); +OccHandle cafWorkSession(const STEPCAFControl_Reader& reader); -Handle_Transfer_FinderProcess cafFinderProcess(const IGESCAFControl_Writer& writer); -Handle_Transfer_FinderProcess cafFinderProcess(const STEPCAFControl_Writer& writer); +OccHandle cafFinderProcess(const IGESCAFControl_Writer& writer); +OccHandle cafFinderProcess(const STEPCAFControl_Writer& writer); bool cafReadFile(IGESCAFControl_Reader& reader, const FilePath& filepath, TaskProgress* progress); bool cafReadFile(STEPCAFControl_Reader& reader, const FilePath& filepath, TaskProgress* progress); diff --git a/src/io_occ/io_occ_gltf_writer.cpp b/src/io_occ/io_occ_gltf_writer.cpp index ca6da673..03cbb836 100644 --- a/src/io_occ/io_occ_gltf_writer.cpp +++ b/src/io_occ/io_occ_gltf_writer.cpp @@ -144,7 +144,7 @@ bool OccGltfWriter::writeFile(const FilePath& filepath, TaskProgress* progress) if (!m_document) return false; - Handle_Message_ProgressIndicator occProgress = new OccProgressIndicator(progress); + OccHandle occProgress = new OccProgressIndicator(progress); const bool isBinary = m_params.format == Format::Binary; RWGltf_CafWriter writer(filepath.u8string().c_str(), isBinary); writer.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(m_params.inputCoordinateSystem); diff --git a/src/io_occ/io_occ_obj_writer.cpp b/src/io_occ/io_occ_obj_writer.cpp index 30123691..4bebcdbf 100644 --- a/src/io_occ/io_occ_obj_writer.cpp +++ b/src/io_occ/io_occ_obj_writer.cpp @@ -68,7 +68,7 @@ bool OccObjWriter::writeFile(const FilePath& filepath, TaskProgress* progress) if (!m_document) return false; - Handle_Message_ProgressIndicator occProgress = new OccProgressIndicator(progress); + OccHandle occProgress = new OccProgressIndicator(progress); RWObj_CafWriter writer(filepath.u8string().c_str()); writer.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(m_params.inputCoordinateSystem); writer.ChangeCoordinateSystemConverter().SetOutputCoordinateSystem(m_params.outputCoordinateSystem); diff --git a/src/io_occ/io_occ_step.cpp b/src/io_occ/io_occ_step.cpp index 85b343af..e67ff7aa 100644 --- a/src/io_occ/io_occ_step.cpp +++ b/src/io_occ/io_occ_step.cpp @@ -8,6 +8,7 @@ #include "io_occ_caf.h" #include "../base/messenger.h" #include "../base/meta_enum.h" +#include "../base/occ_handle.h" #include "../base/occ_static_variables_rollback.h" #include "../base/property_builtins.h" #include "../base/property_enumeration.h" @@ -232,12 +233,14 @@ class OccStepWriter::Properties : public PropertyGroup { this->lengthUnit.setDescription( textIdTr("Defines a unit in which the STEP file should be written. If set to " "unit other than millimeter, the model is converted to these units " - "during the translation")); + "during the translation") + ); this->freeVertexMode.setDescription( textIdTr("Parameter to write all free vertices in one SDR (name and style of " "vertex are lost) or each vertex in its own SDR (name and style of " - "vertex are exported)")); + "vertex are exported)") + ); this->freeVertexMode.setDescriptions({ { FreeVertexMode::Compound, textIdTr("All free vertices are united into one " "compound and exported in one shape definition representation (vertex name " @@ -250,11 +253,13 @@ class OccStepWriter::Properties : public PropertyGroup { this->writePCurves.setDescription( textIdTr("Indicates whether parametric curves (curves in parametric space of surface) should be " "written into the STEP file.\n" - "It can be disabled in order to minimize the size of the resulting file.")); + "It can be disabled in order to minimize the size of the resulting file.") + ); this->writeSubShapesNames.setDescription( textIdTr("Indicates whether to write sub-shape names to 'Name' attributes of " - "STEP Representation Items")); + "STEP Representation Items") + ); this->headerAuthor.setDescription(textIdTr("Author attribute in STEP header")); this->headerOrganization.setDescription(textIdTr("Organization(of author) attribute in STEP header")); @@ -334,7 +339,6 @@ bool OccStepWriter::writeFile(const FilePath& filepath, TaskProgress* /*progress makeHeader.SetOrganizationValue(1, to_OccHandleHAsciiString(m_params.headerOrganization)); makeHeader.SetOriginatingSystem(to_OccHandleHAsciiString(m_params.headerOriginatingSystem)); makeHeader.SetDescriptionValue(1, to_OccHandleHAsciiString(m_params.headerDescription)); - const IFSelect_ReturnStatus err = m_writer->Write(filepath.u8string().c_str()); return err == IFSelect_RetDone; } diff --git a/src/io_occ/io_occ_stl.cpp b/src/io_occ/io_occ_stl.cpp index 869bd3b8..ab661cd1 100644 --- a/src/io_occ/io_occ_stl.cpp +++ b/src/io_occ/io_occ_stl.cpp @@ -73,7 +73,7 @@ class OccStlWriter::Properties : public PropertyGroup { bool OccStlReader::readFile(const FilePath& filepath, TaskProgress* progress) { - Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); + OccHandle indicator = new OccProgressIndicator(progress); m_baseFilename = filepath.stem(); m_mesh = RWStl::ReadFile(filepath.u8string().c_str(), TKernelUtils::start(indicator)); return !m_mesh.IsNull(); @@ -131,7 +131,7 @@ bool OccStlWriter::writeFile(const FilePath& filepath, TaskProgress* progress) writer.ASCIIMode() = m_params.format == Format::Ascii; const std::string strFilepath = filepath.u8string(); #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) - Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); + OccHandle indicator = new OccProgressIndicator(progress); return writer.Write(m_shape, strFilepath.c_str(), TKernelUtils::start(indicator)); #else MAYO_UNUSED(progress); diff --git a/src/io_occ/io_occ_stl.h b/src/io_occ/io_occ_stl.h index 90df8723..365ff999 100644 --- a/src/io_occ/io_occ_stl.h +++ b/src/io_occ/io_occ_stl.h @@ -8,6 +8,7 @@ #include "../base/io_reader.h" #include "../base/io_writer.h" +#include "../base/occ_handle.h" #include #include @@ -22,7 +23,7 @@ class OccStlReader : public Reader { void applyProperties(const PropertyGroup*) override {} private: - Handle_Poly_Triangulation m_mesh; + OccHandle m_mesh; FilePath m_baseFilename; }; diff --git a/src/io_off/io_off_reader.cpp b/src/io_off/io_off_reader.cpp index e3572b84..2318d004 100644 --- a/src/io_off/io_off_reader.cpp +++ b/src/io_off/io_off_reader.cpp @@ -315,7 +315,7 @@ TDF_Label OffReader::transferMesh(DocumentPtr doc, TaskProgress* progress) triangleCount += facet.vertexCount - 2; // Create mesh object - Handle_Poly_Triangulation mesh = new Poly_Triangulation(vertexCount, triangleCount, false/*!hasUvNodes*/); + OccHandle mesh = new Poly_Triangulation(vertexCount, triangleCount, false/*!hasUvNodes*/); // Helper function for progress report auto fnUpdateProgress = [=](int current) { diff --git a/src/io_off/io_off_writer.cpp b/src/io_off/io_off_writer.cpp index 19519e65..8c450547 100644 --- a/src/io_off/io_off_writer.cpp +++ b/src/io_off/io_off_writer.cpp @@ -73,7 +73,7 @@ bool OffWriter::writeFile(const FilePath& filepath, TaskProgress* progress) for (const DocumentTreeNode& treeNode : m_vecTreeNode) { IMeshAccess_visitMeshes(treeNode, [&](const IMeshAccess& mesh) { const gp_Trsf& meshTrsf = mesh.location().Transformation(); - const Handle(Poly_Triangulation)& triangulation = mesh.triangulation(); + const OccHandle& triangulation = mesh.triangulation(); for (int i = 1; i <= triangulation->NbNodes(); ++i) { const gp_Pnt pnt = triangulation->Node(i).Transformed(meshTrsf); const std::optional color = mesh.nodeColor(i - 1); @@ -96,7 +96,7 @@ bool OffWriter::writeFile(const FilePath& filepath, TaskProgress* progress) int ifacet = 0; for (const DocumentTreeNode& treeNode : m_vecTreeNode) { IMeshAccess_visitMeshes(treeNode, [&](const IMeshAccess& mesh) { - const Handle(Poly_Triangulation)& triangulation = mesh.triangulation(); + const OccHandle& triangulation = mesh.triangulation(); for (int i = 1; i <= triangulation->NbTriangles(); ++i) { const Poly_Triangle& tri = triangulation->Triangle(i); fstr << "3 " diff --git a/src/io_ply/io_ply_reader.cpp b/src/io_ply/io_ply_reader.cpp index 67930b10..c2662c02 100644 --- a/src/io_ply/io_ply_reader.cpp +++ b/src/io_ply/io_ply_reader.cpp @@ -155,7 +155,7 @@ TDF_Label PlyReader::transferMesh(DocumentPtr doc, TaskProgress* /*progress*/) { // Create target mesh const int triangleCount = CppUtils::safeStaticCast(m_vecIndex.size() / 3); - Handle_Poly_Triangulation mesh = new Poly_Triangulation(m_nodeCount, triangleCount, false/*hasUvNodes*/); + OccHandle mesh = new Poly_Triangulation(m_nodeCount, triangleCount, false/*hasUvNodes*/); if (!m_vecNormalCoord.empty()) MeshUtils::allocateNormals(mesh); diff --git a/src/io_ply/io_ply_writer.cpp b/src/io_ply/io_ply_writer.cpp index 3231db4e..ef4b4743 100644 --- a/src/io_ply/io_ply_writer.cpp +++ b/src/io_ply/io_ply_writer.cpp @@ -260,7 +260,7 @@ void PlyWriter::applyProperties(const PropertyGroup* params) void PlyWriter::addMesh(const IMeshAccess& mesh) { - const Handle(Poly_Triangulation)& triangulation = mesh.triangulation(); + const OccHandle& triangulation = mesh.triangulation(); for (int i = 1; i <= triangulation->NbTriangles(); ++i) { const Poly_Triangle& triangle = triangulation->Triangle(i); const int32_t offset = CppUtils::safeStaticCast(m_vecNode.size()); @@ -286,7 +286,7 @@ void PlyWriter::addMesh(const IMeshAccess& mesh) void PlyWriter::addPointCloud(const PointCloudDataPtr& pntCloud) { - const Handle(Graphic3d_ArrayOfPoints)& points = pntCloud->points(); + const OccHandle& points = pntCloud->points(); const int pntCount = points->VertexNumber(); for (int i = 1; i <= pntCount; ++i) { const Vertex vertex = PlyWriter::toVertex(points->Vertice(i)); diff --git a/src/measure/measure_display.cpp b/src/measure/measure_display.cpp index 6ce4e81e..2ae9a285 100644 --- a/src/measure/measure_display.cpp +++ b/src/measure/measure_display.cpp @@ -130,7 +130,7 @@ std::string BaseMeasureDisplay::graphicsText(const gp_Pnt& pnt, const MeasureDis return BaseMeasureDisplay; } -void BaseMeasureDisplay::adaptScale(const Handle_AIS_TextLabel& gfxText, const MeasureDisplayConfig& config) +void BaseMeasureDisplay::adaptScale(const OccHandle& gfxText, const MeasureDisplayConfig& config) { static const Prs3d_TextAspect defaultTextAspect; gfxText->SetHeight(defaultTextAspect.Height() * config.devicePixelRatio); @@ -141,7 +141,7 @@ void BaseMeasureDisplay::applyGraphicsDefaults(IMeasureDisplay* measureDisplay) for (int i = 0; i < measureDisplay->graphicsObjectsCount(); ++i) { auto gfxObject = measureDisplay->graphicsObjectAt(i); gfxObject->SetZLayer(Graphic3d_ZLayerId_Topmost); - auto gfxText = Handle(AIS_TextLabel)::DownCast(gfxObject); + auto gfxText = OccHandle::DownCast(gfxObject); if (gfxText) { gfxText->SetDisplayType(Aspect_TODT_SUBTITLE); gfxText->SetColorSubTitle(Quantity_NOC_BLACK); @@ -343,7 +343,7 @@ MeasureDisplayAngle::MeasureDisplayAngle(MeasureAngle angle) const gp_Vec vec1(angle.pntCenter, angle.pnt1); const gp_Vec vec2(angle.pntCenter, angle.pnt2); const gp_Ax2 axCircle(angle.pntCenter, vec1.Crossed(vec2), vec1); - Handle_Geom_Circle geomCircle = new Geom_Circle(axCircle, 0.8 * vec1.Magnitude()); + OccHandle geomCircle = new Geom_Circle(axCircle, 0.8 * vec1.Magnitude()); const double param1 = ElCLib::Parameter(geomCircle->Circ(), angle.pnt1); const double param2 = ElCLib::Parameter(geomCircle->Circ(), angle.pnt2); m_gfxAngle = new AIS_Circle(geomCircle, param1, param2); diff --git a/src/measure/measure_display.h b/src/measure/measure_display.h index 4cb70713..8c545726 100644 --- a/src/measure/measure_display.h +++ b/src/measure/measure_display.h @@ -90,7 +90,7 @@ class BaseMeasureDisplay : public IMeasureDisplay { static std::string text(const gp_Pnt& pnt, const MeasureDisplayConfig& config); static std::string text(double value, const MeasureDisplayConfig& config); static std::string graphicsText(const gp_Pnt& pnt, const MeasureDisplayConfig& config); - static void adaptScale(const Handle_AIS_TextLabel& gfxText, const MeasureDisplayConfig& config); + static void adaptScale(const OccHandle& gfxText, const MeasureDisplayConfig& config); static void applyGraphicsDefaults(IMeasureDisplay* measureDisplay); @@ -112,7 +112,7 @@ class MeasureDisplayVertex : public BaseMeasureDisplay { private: gp_Pnt m_pnt; - Handle_AIS_TextLabel m_gfxText; + OccHandle m_gfxText; }; // -- @@ -128,9 +128,9 @@ class MeasureDisplayCircleCenter : public BaseMeasureDisplay { private: gp_Circ m_circle; - Handle_AIS_Point m_gfxPoint; - Handle_AIS_TextLabel m_gfxText; - Handle_AIS_Circle m_gfxCircle; + OccHandle m_gfxPoint; + OccHandle m_gfxText; + OccHandle m_gfxCircle; }; // -- @@ -148,9 +148,9 @@ class MeasureDisplayCircleDiameter : public BaseMeasureDisplay { static gp_Pnt diameterOpposedPnt(const gp_Pnt& pntOnCircle, const gp_Circ& circ); gp_Circ m_circle; - Handle_AIS_Circle m_gfxCircle; - Handle_AIS_Line m_gfxDiameter; - Handle_AIS_TextLabel m_gfxDiameterText; + OccHandle m_gfxCircle; + OccHandle m_gfxDiameter; + OccHandle m_gfxDiameterText; }; // -- @@ -166,10 +166,10 @@ class MeasureDisplayDistance : public BaseMeasureDisplay { private: MeasureDistance m_dist; - Handle_AIS_Line m_gfxLength; - Handle_AIS_TextLabel m_gfxDistText; - Handle_AIS_Point m_gfxPnt1; - Handle_AIS_Point m_gfxPnt2; + OccHandle m_gfxLength; + OccHandle m_gfxDistText; + OccHandle m_gfxPnt1; + OccHandle m_gfxPnt2; }; // -- @@ -185,10 +185,10 @@ class MeasureDisplayAngle : public BaseMeasureDisplay { private: MeasureAngle m_angle; - Handle_AIS_Line m_gfxEntity1; - Handle_AIS_Line m_gfxEntity2; - Handle_AIS_Circle m_gfxAngle; - Handle_AIS_TextLabel m_gfxAngleText; + OccHandle m_gfxEntity1; + OccHandle m_gfxEntity2; + OccHandle m_gfxAngle; + OccHandle m_gfxAngleText; }; // -- diff --git a/src/measure/measure_tool_brep.cpp b/src/measure/measure_tool_brep.cpp index 755ba340..b53dbbb8 100644 --- a/src/measure/measure_tool_brep.cpp +++ b/src/measure/measure_tool_brep.cpp @@ -10,6 +10,7 @@ #include "../base/geom_utils.h" #include "../base/math_utils.h" #include "../base/mesh_utils.h" +#include "../base/occ_handle.h" #include "../base/text_id.h" #include "../graphics/graphics_shape_object_driver.h" @@ -109,7 +110,7 @@ template void throwErrorIf(bool cond) const TopoDS_Shape getShape(const GraphicsOwnerPtr& owner) { static const TopoDS_Shape nullShape; - auto brepOwner = Handle_StdSelect_BRepOwner::DownCast(owner); + auto brepOwner = OccHandle::DownCast(owner); TopLoc_Location ownerLoc = owner->Location(); #if OCC_VERSION_HEX >= 0x070600 // Force scale factor to 1 @@ -307,7 +308,7 @@ MeasureCircle MeasureToolBRep::brepCircleFromGeometricEdge(const TopoDS_Edge& ed MeasureCircle MeasureToolBRep::brepCircleFromPolygonEdge(const TopoDS_Edge& edge) { TopLoc_Location loc; - const Handle(Poly_Polygon3D)& polyline = BRep_Tool::Polygon3D(edge, loc); + const OccHandle& polyline = BRep_Tool::Polygon3D(edge, loc); throwErrorIf(polyline.IsNull() || polyline->NbNodes() < 7); // Try to create a circle from 3 sample points const GC_MakeCircle makeCirc( @@ -463,7 +464,7 @@ MeasureLength MeasureToolBRep::brepLength(const TopoDS_Shape& shape) } else { TopLoc_Location loc; - const Handle(Poly_Polygon3D)& polyline = BRep_Tool::Polygon3D(edge, loc); + const OccHandle& polyline = BRep_Tool::Polygon3D(edge, loc); throwErrorIf(polyline.IsNull()); double len = 0.; // Compute length of the polygon diff --git a/src/qtcommon/qstring_conv.h b/src/qtcommon/qstring_conv.h index c3311a7f..3bcf4d17 100644 --- a/src/qtcommon/qstring_conv.h +++ b/src/qtcommon/qstring_conv.h @@ -65,8 +65,8 @@ template<> struct StringConv { }; // Handle(TCollection_HAsciiString) -> QString -template<> struct StringConv { - static auto to(const Handle(TCollection_HAsciiString)& str) { +template<> struct StringConv, QString> { + static auto to(const OccHandle& str) { return string_conv(str ? str->String() : TCollection_AsciiString()); } }; @@ -126,9 +126,9 @@ template<> struct StringConv { }; // QString -> Handle(TCollection_HAsciiString) -template<> struct StringConv { +template<> struct StringConv> { static auto to(const QString& str) { - Handle(TCollection_HAsciiString) hnd = new TCollection_HAsciiString(qUtf8Printable(str)); + OccHandle hnd = new TCollection_HAsciiString(qUtf8Printable(str)); return hnd; } }; diff --git a/tests/test_base.cpp b/tests/test_base.cpp index 44aef516..e586abd9 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -848,7 +848,7 @@ void TestBase::MeshUtils_test() int countTriangle = 0; BRepUtils::forEachSubFace(shapeBox, [&](const TopoDS_Face& face) { TopLoc_Location loc; - const Handle_Poly_Triangulation& polyTri = BRep_Tool::Triangulation(face, loc); + const OccHandle& polyTri = BRep_Tool::Triangulation(face, loc); if (!polyTri.IsNull()) { countNode += polyTri->NbNodes(); countTriangle += polyTri->NbTriangles(); @@ -856,13 +856,13 @@ void TestBase::MeshUtils_test() }); // Merge all face triangulations into one - Handle_Poly_Triangulation polyTriBox = new Poly_Triangulation(countNode, countTriangle, false); + OccHandle polyTriBox = new Poly_Triangulation(countNode, countTriangle, false); { int idNodeOffset = 0; int idTriangleOffset = 0; BRepUtils::forEachSubFace(shapeBox, [&](const TopoDS_Face& face) { TopLoc_Location loc; - const Handle_Poly_Triangulation& polyTri = BRep_Tool::Triangulation(face, loc); + const OccHandle& polyTri = BRep_Tool::Triangulation(face, loc); if (!polyTri.IsNull()) { for (int i = 1; i <= polyTri->NbNodes(); ++i) MeshUtils::setNode(polyTriBox, idNodeOffset + i, polyTri->Node(i)); From 93eeabc62bf44ce2e93fafa23c7320ac2fce580f Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Tue, 30 Apr 2024 17:30:59 +0200 Subject: [PATCH 065/101] Base: add makeOccHandle() utility --- src/base/occ_handle.h | 9 +++++++++ tests/test_base.cpp | 37 +++++++++++++++++++++++++++++++++++++ tests/test_base.h | 2 ++ 3 files changed, 48 insertions(+) diff --git a/src/base/occ_handle.h b/src/base/occ_handle.h index 387a2243..1a4decbf 100644 --- a/src/base/occ_handle.h +++ b/src/base/occ_handle.h @@ -7,10 +7,19 @@ #pragma once #include +#include // For std::forward() namespace Mayo { // Template alias for OpenCascade handle template using OccHandle = opencascade::handle; +// Constructs an object of 'T' wrapped in an OpenCascade handle +// Note: Standard_Transient must be a base class of 'T' +template +OccHandle makeOccHandle(Args&&... args) +{ + return new T(std::forward(args)...); +} + } // namespace Mayo diff --git a/tests/test_base.cpp b/tests/test_base.cpp index e586abd9..ec9b3834 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -23,6 +23,7 @@ #include "../src/base/io_system.h" #include "../src/base/occ_static_variables_rollback.h" #include "../src/base/libtree.h" +#include "../src/base/occ_handle.h" #include "../src/base/mesh_utils.h" #include "../src/base/meta_enum.h" #include "../src/base/property_builtins.h" @@ -66,6 +67,7 @@ #include #include #include +#include #include #include #include @@ -310,6 +312,41 @@ void TestBase::FilePath_test() } } +void TestBase::OccHandle_test() +{ + { + struct OccHandleTestClass_0 : public Standard_Transient { + explicit OccHandleTestClass_0() = default; + }; + + auto hnd = makeOccHandle(); + QCOMPARE(typeid(hnd), typeid(OccHandle)); + QVERIFY(hnd.get() != nullptr); + } + + { + struct OccHandleTestClass_1 : public Standard_Transient { + explicit OccHandleTestClass_1() = default; + explicit OccHandleTestClass_1(const std::string& str) : m_str(str) {} + std::string m_str; + }; + + { + auto hnd = makeOccHandle(); + QCOMPARE(typeid(hnd), typeid(OccHandle)); + QVERIFY(hnd.get() != nullptr); + QCOMPARE(hnd->m_str, std::string{}); + } + + { + auto hnd = makeOccHandle("Test string value"); + QCOMPARE(typeid(hnd), typeid(OccHandle)); + QVERIFY(hnd.get() != nullptr); + QCOMPARE(hnd->m_str, "Test string value"); + } + } +} + void TestBase::PropertyValueConversionVariant_doubleToInt_test() { using Variant = PropertyValueConversion::Variant; diff --git a/tests/test_base.h b/tests/test_base.h index 5723fe7e..d90a9f3f 100644 --- a/tests/test_base.h +++ b/tests/test_base.h @@ -26,6 +26,8 @@ private slots: void FilePath_test(); + void OccHandle_test(); + void PropertyValueConversionVariant_doubleToInt_test(); void PropertyValueConversionVariant_doubleToInt_test_data(); From c1fa59b9385ea872ebdeb42eefbf87da83799d3f Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Tue, 30 Apr 2024 17:31:19 +0200 Subject: [PATCH 066/101] IO_Assimp: use OccHandle and makeOccHandle() --- src/io_assimp/io_assimp_reader.cpp | 32 +++++++++++++++--------------- src/io_assimp/io_assimp_reader.h | 15 +++++++------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/io_assimp/io_assimp_reader.cpp b/src/io_assimp/io_assimp_reader.cpp index 92a9ae82..7b61b7f8 100644 --- a/src/io_assimp/io_assimp_reader.cpp +++ b/src/io_assimp/io_assimp_reader.cpp @@ -94,7 +94,7 @@ bool deep_aiNodeTransformationHasScaling(const aiNode* node) || scaling.x < 0. || scaling.y < 0. || scaling.z < 0. ) { - //std::cout << "[TRACE] hasScaling: " << scaling.x << " " << scaling.y << " " < createOccTexture(const aiTexture* texture) { const auto textureWidth = texture->mWidth; const auto textureHeight = texture->mHeight; const auto textureSize = textureHeight == 0 ? textureWidth : 4 * textureWidth * textureHeight; - Handle(NCollection_Buffer) buff = new NCollection_Buffer( + auto buff = makeOccHandle( NCollection_BaseAllocator::CommonBaseAllocator(), textureSize ); @@ -151,14 +151,14 @@ Handle(Image_Texture) createOccTexture(const aiTexture* texture) // Create an OpenCascade Poly_Triangulation object from assimp mesh // The input 'mesh' is assumed to contain only triangles -Handle(Poly_Triangulation) createOccTriangulation(const aiMesh* mesh) +OccHandle createOccTriangulation(const aiMesh* mesh) { assert(mesh != nullptr); assert(mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE); const unsigned textureIndex = 0; const bool hasUvNodes = mesh->HasTextureCoords(textureIndex) && mesh->mNumUVComponents[textureIndex] == 2; - Handle(Poly_Triangulation) triangulation = new Poly_Triangulation(mesh->mNumVertices, mesh->mNumFaces, hasUvNodes); + auto triangulation = makeOccHandle(mesh->mNumVertices, mesh->mNumFaces, hasUvNodes); for (unsigned i = 0; i < mesh->mNumVertices; ++i) { const aiVector3D& vertex = mesh->mVertices[i]; @@ -340,7 +340,7 @@ TDF_LabelSequence AssimpReader::transfer(DocumentPtr doc, TaskProgress* progress // Add materials in target document auto materialTool = doc->xcaf().visMaterialTool(); - for (const Handle_XCAFDoc_VisMaterial& material : m_vecMaterial) { + for (const OccHandle& material : m_vecMaterial) { const TDF_Label label = materialTool->AddMaterial(material, material->RawName()->String()); m_mapMaterialLabel.insert({ material, label }); } @@ -370,7 +370,7 @@ void AssimpReader::applyProperties(const PropertyGroup* group) } } -Handle(Image_Texture) AssimpReader::findOccTexture( +OccHandle AssimpReader::findOccTexture( const std::string& strFilepath, const FilePath& modelFilepath ) { @@ -378,14 +378,14 @@ Handle(Image_Texture) AssimpReader::findOccTexture( { // Note: aiScene::GetEmbeddedTextureAndIndex() isn't available for version < 5.1 const aiTexture* texture = m_scene->GetEmbeddedTexture(strFilepath.c_str()); - Handle(Image_Texture) occTexture = Cpp::findValue(texture, m_mapEmbeddedTexture); + OccHandle occTexture = Cpp::findValue(texture, m_mapEmbeddedTexture); if (occTexture) return occTexture; } // Texture might have already been loaded from file { - Handle(Image_Texture) texture = CppUtils::findValue(strFilepath, m_mapFileTexture); + OccHandle texture = CppUtils::findValue(strFilepath, m_mapFileTexture); if (texture) return texture; } @@ -410,7 +410,7 @@ Handle(Image_Texture) AssimpReader::findOccTexture( // Could find an existing filepath for the texture if (ptrTextureFilepath) { - Handle(Image_Texture) texture = new Image_Texture(filepathTo(*ptrTextureFilepath)); + auto texture = makeOccHandle(filepathTo(*ptrTextureFilepath)); // Cache texture m_mapFileTexture.insert({ strFilepath, texture }); return texture; @@ -425,11 +425,11 @@ Handle(Image_Texture) AssimpReader::findOccTexture( return {}; } -Handle(XCAFDoc_VisMaterial) AssimpReader::createOccVisMaterial( +OccHandle AssimpReader::createOccVisMaterial( const aiMaterial* material, const FilePath& modelFilepath ) { - Handle(XCAFDoc_VisMaterial) mat = new XCAFDoc_VisMaterial; + auto mat = makeOccHandle(); //mat->SetAlphaMode(Graphic3d_AlphaMode_Opaque); @@ -448,7 +448,7 @@ Handle(XCAFDoc_VisMaterial) AssimpReader::createOccVisMaterial( aiString matName; material->Get(AI_MATKEY_NAME, matName); std::string_view vMatName{ matName.C_Str(), matName.length }; - mat->SetRawName(string_conv(vMatName)); + mat->SetRawName(string_conv>(vMatName)); } // Backface culling diff --git a/src/io_assimp/io_assimp_reader.h b/src/io_assimp/io_assimp_reader.h index 6593b7d9..41845f64 100644 --- a/src/io_assimp/io_assimp_reader.h +++ b/src/io_assimp/io_assimp_reader.h @@ -8,6 +8,7 @@ #include "../base/document_ptr.h" #include "../base/io_reader.h" +#include "../base/occ_handle.h" #include "../base/tkernel_utils.h" #include @@ -42,11 +43,11 @@ class AssimpReader : public Reader { // Create OpenCascade texture object // Parameter 'strFilepath' is the filepath to the texture as specified by the assimp material // Parameter 'modelFilepath' is the filepath to the 3D model being imported with Reader::readFile() - Handle(Image_Texture) findOccTexture(const std::string& strFilepath, const FilePath& modelFilepath); + OccHandle findOccTexture(const std::string& strFilepath, const FilePath& modelFilepath); // Create XCAFDoc_VisMaterial from assimp material // Parameter 'modelFilepath' is the filepath to the 3D model being imported with Reader::readFile() - Handle(XCAFDoc_VisMaterial) createOccVisMaterial(const aiMaterial* material, const FilePath& modelFilepath); + OccHandle createOccVisMaterial(const aiMaterial* material, const FilePath& modelFilepath); void transferSceneNode( const aiNode* node, @@ -64,12 +65,12 @@ class AssimpReader : public Reader { Assimp::Importer m_importer; const aiScene* m_scene = nullptr; - std::vector m_vecTriangulation; - std::vector m_vecMaterial; - std::unordered_map m_mapMaterialLabel; + std::vector> m_vecTriangulation; + std::vector> m_vecMaterial; + std::unordered_map, TDF_Label> m_mapMaterialLabel; std::unordered_map m_mapNodeData; - std::unordered_map m_mapEmbeddedTexture; - std::unordered_map m_mapFileTexture; + std::unordered_map> m_mapEmbeddedTexture; + std::unordered_map> m_mapFileTexture; }; } // namespace IO From 5711be4a61ed5d5b83187ec6969c345dd2494f42 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Tue, 30 Apr 2024 19:16:51 +0200 Subject: [PATCH 067/101] Extensive usage of OccHandle and makeOccHandle() --- src/app/dialog_inspect_xde.cpp | 2 +- src/app/widget_clip_planes.cpp | 2 +- src/app/widget_grid.cpp | 14 +++++------ src/app/widget_gui_document.cpp | 2 +- src/app/widget_occ_view.cpp | 2 +- src/app/widget_occ_view_impl.cpp | 2 +- src/base/brep_utils.cpp | 5 ++-- src/base/brep_utils.h | 2 +- src/base/string_conv.h | 6 ++--- src/cli/main.cpp | 4 ++-- src/graphics/graphics_create_driver.cpp | 2 +- src/graphics/graphics_mesh_object_driver.cpp | 2 +- src/graphics/graphics_scene.cpp | 4 ++-- src/graphics/graphics_scene.h | 4 ++-- src/graphics/graphics_shape_object_driver.cpp | 2 +- src/graphics/graphics_view_ptr.h | 8 +++---- src/gui/gui_document.cpp | 23 ++++++++++--------- src/io_dxf/io_dxf.cpp | 4 ++-- src/io_image/io_image.cpp | 2 +- src/io_occ/io_occ_base_mesh.cpp | 2 +- src/io_occ/io_occ_brep.cpp | 13 ++++++----- src/io_occ/io_occ_caf.cpp | 4 ++-- src/io_occ/io_occ_gltf_writer.cpp | 2 +- src/io_occ/io_occ_obj_writer.cpp | 2 +- src/io_occ/io_occ_stl.cpp | 4 ++-- src/io_off/io_off_reader.cpp | 2 +- src/io_ply/io_ply_reader.cpp | 2 +- src/measure/measure_display.cpp | 8 +++---- src/measure/measure_display.h | 20 ++++++++-------- src/measure/measure_tool_brep.cpp | 2 +- src/qtcommon/qstring_conv.h | 3 +-- tests/test_app.cpp | 2 +- tests/test_base.cpp | 2 +- 33 files changed, 80 insertions(+), 80 deletions(-) diff --git a/src/app/dialog_inspect_xde.cpp b/src/app/dialog_inspect_xde.cpp index 94ebaa9d..56d4ef4f 100644 --- a/src/app/dialog_inspect_xde.cpp +++ b/src/app/dialog_inspect_xde.cpp @@ -429,7 +429,7 @@ static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const OccHan else if (imgTexture->DataBuffer() && !imgTexture->DataBuffer()->IsEmpty()) { // Texture is provided by some embedded data item->setText(1, DialogInspectXde::tr("")); - const Handle(NCollection_Buffer)& buff = imgTexture->DataBuffer(); + const OccHandle& buff = imgTexture->DataBuffer(); item->setImage(1, QByteArray::fromRawData(reinterpret_cast(buff->Data()), buff->Size())); } diff --git a/src/app/widget_clip_planes.cpp b/src/app/widget_clip_planes.cpp index c2e5889a..14b9311c 100644 --- a/src/app/widget_clip_planes.cpp +++ b/src/app/widget_clip_planes.cpp @@ -242,7 +242,7 @@ void WidgetClipPlanes::createPlaneCappingTexture() const QByteArray fileContents = file.readAll(); const QByteArray filenameUtf8 = file.fileName().toUtf8(); auto fileContentsData = reinterpret_cast(fileContents.constData()); - OccHandle imageCapping = new Image_AlienPixMap; + auto imageCapping = makeOccHandle(); imageCapping->Load(fileContentsData, fileContents.size(), filenameUtf8.constData()); m_textureCapping = new GraphicsTexture2D(imageCapping); m_textureCapping->EnableModulate(); diff --git a/src/app/widget_grid.cpp b/src/app/widget_grid.cpp index 3c521680..5501ea1f 100644 --- a/src/app/widget_grid.cpp +++ b/src/app/widget_grid.cpp @@ -37,7 +37,7 @@ WidgetGrid::WidgetGrid(GraphicsViewPtr viewPtr, QWidget* parent) m_ui(new Ui_WidgetGrid), m_viewPtr(viewPtr) { - const Handle_V3d_Viewer& viewer = viewPtr->Viewer(); + const OccHandle& viewer = viewPtr->Viewer(); // Intial configuration m_ui->setupUi(this); @@ -128,7 +128,7 @@ WidgetGrid::WidgetGrid(GraphicsViewPtr viewPtr, QWidget* parent) m_ui->combo_Plane->setCurrentIndex(3); // Install grid draw mode - Handle_Aspect_Grid gridAspect = GraphicsUtils::V3dViewer_grid(viewer); + OccHandle gridAspect = GraphicsUtils::V3dViewer_grid(viewer); if (gridAspect) { if (gridAspect->DrawMode() == Aspect_GDM_Lines) m_ui->combo_DrawMode->setCurrentIndex(0); @@ -248,7 +248,7 @@ const gp_Ax2& WidgetGrid::toPlaneAxis(int comboBoxItemIndex) void WidgetGrid::activateGrid(bool on) { - const Handle_V3d_Viewer& viewer = m_viewPtr->Viewer(); + const OccHandle& viewer = m_viewPtr->Viewer(); if (on) { viewer->ActivateGrid( toGridType(m_ui->combo_Type->currentIndex()), @@ -269,7 +269,7 @@ void WidgetGrid::applyGridParams() auto fnCorrectedGridStep = [](double gridStep) { return !qFuzzyIsNull(gridStep) ? gridStep : 0.01; }; - const Handle_V3d_Viewer& viewer = m_viewPtr->Viewer(); + const OccHandle& viewer = m_viewPtr->Viewer(); auto gridType = toGridType(m_ui->combo_Type->currentIndex()); if (gridType == Aspect_GT_Rectangular) { viewer->SetRectangularGridValues( @@ -295,7 +295,7 @@ void WidgetGrid::applyGridParams() void WidgetGrid::applyGridGraphicsParams() { - const Handle_V3d_Viewer& viewer = m_viewPtr->Viewer(); + const OccHandle& viewer = m_viewPtr->Viewer(); auto gridType = toGridType(m_ui->combo_Type->currentIndex()); if (gridType == Aspect_GT_Rectangular) { viewer->SetRectangularGridGraphicValues( @@ -316,7 +316,7 @@ void WidgetGrid::applyGridGraphicsParams() void WidgetGrid::chooseGridColor(GridColorType colorType) { - const Handle_V3d_Viewer& viewer = m_viewPtr->Viewer(); + const OccHandle& viewer = m_viewPtr->Viewer(); auto gridColors = GraphicsUtils::V3dViewer_gridColors(viewer); // Helper function to apply some base/tenth grid color auto fnApplyGridColor = [=](const Quantity_Color& color) { @@ -356,7 +356,7 @@ void WidgetGrid::chooseGridColor(GridColorType colorType) void WidgetGrid::enableGridColorTenth(bool on) { - const Handle_V3d_Viewer& viewer = m_viewPtr->Viewer(); + const OccHandle& viewer = m_viewPtr->Viewer(); m_ui->label_ColorTenth->setEnabled(on); m_ui->btn_ColorTenth->setEnabled(on); auto gridColors = GraphicsUtils::V3dViewer_gridColors(viewer); diff --git a/src/app/widget_gui_document.cpp b/src/app/widget_gui_document.cpp index 87218f58..21820e96 100644 --- a/src/app/widget_gui_document.cpp +++ b/src/app/widget_gui_document.cpp @@ -117,7 +117,7 @@ WidgetGuiDocument::WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent) m_widgetBtns = this->createWidgetPanelContainer(widgetBtnsContents); auto gfxScene = m_guiDoc->graphicsScene(); - gfxScene->signalRedrawRequested.connectSlot([=](const Handle_V3d_View& view) { + gfxScene->signalRedrawRequested.connectSlot([=](const OccHandle& view) { if (view == m_qtOccView->v3dView()) m_qtOccView->redraw(); }); diff --git a/src/app/widget_occ_view.cpp b/src/app/widget_occ_view.cpp index 84d6c201..02a80eb5 100644 --- a/src/app/widget_occ_view.cpp +++ b/src/app/widget_occ_view.cpp @@ -184,7 +184,7 @@ QWidgetOccView* QWidgetOccView::create(const OccHandle& view, QWidget* void QWidgetOccView::showEvent(QShowEvent*) { if (this->v3dView()->Window().IsNull()) { - OccHandle hWnd = new OcctWindow(this); + auto hWnd = makeOccHandle(this); this->v3dView()->SetWindow(hWnd); if (!hWnd->IsMapped()) hWnd->Map(); diff --git a/src/app/widget_occ_view_impl.cpp b/src/app/widget_occ_view_impl.cpp index 9a61c541..435fa47a 100644 --- a/src/app/widget_occ_view_impl.cpp +++ b/src/app/widget_occ_view_impl.cpp @@ -63,7 +63,7 @@ bool QOpenGLWidgetOccView_isCoreProfile() void QOpenGLWidgetOccView_createOpenGlContext(std::function fnCallback) { - OccHandle glCtx = new OpenGl_Context; + auto glCtx = makeOccHandle(); if (!glCtx->Init(QOpenGLWidgetOccView_isCoreProfile())) { Message::SendFail() << "Error: OpenGl_Context is unable to wrap OpenGL context"; return; diff --git a/src/base/brep_utils.cpp b/src/base/brep_utils.cpp index 4aba8bbb..ed98d901 100644 --- a/src/base/brep_utils.cpp +++ b/src/base/brep_utils.cpp @@ -100,10 +100,11 @@ bool BRepUtils::isGeometric(const TopoDS_Face& face) } void BRepUtils::computeMesh( - const TopoDS_Shape& shape, const OccBRepMeshParameters& params, TaskProgress* progress) + const TopoDS_Shape& shape, const OccBRepMeshParameters& params, TaskProgress* progress + ) { #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) - OccHandle indicator = new OccProgressIndicator(progress); + auto indicator = makeOccHandle(progress); BRepMesh_IncrementalMesh mesher(shape, params, TKernelUtils::start(indicator)); #else BRepMesh_IncrementalMesh mesher(shape, params); diff --git a/src/base/brep_utils.h b/src/base/brep_utils.h index 57ae6a8f..ee710974 100644 --- a/src/base/brep_utils.h +++ b/src/base/brep_utils.h @@ -30,7 +30,7 @@ struct BRepUtils { static void addShape(TopoDS_Shape* ptrTargetShape, const TopoDS_Shape& shape); // Creates a non-geometric TopoDS_Edge wrapping 'polygon' - static TopoDS_Edge makeEdge(const Handle(Poly_Polygon3D)& polygon); + static TopoDS_Edge makeEdge(const OccHandle& polygon); // Creates a non-geometric TopoDS_Face wrapping triangulation 'mesh' static TopoDS_Face makeFace(const OccHandle& mesh); diff --git a/src/base/string_conv.h b/src/base/string_conv.h index f1e134d2..48bc249a 100644 --- a/src/base/string_conv.h +++ b/src/base/string_conv.h @@ -135,8 +135,7 @@ template<> struct StringConv { // std::string_view -> Handle(TCollection_HAsciiString) template<> struct StringConv> { static auto to(std::string_view str) { - OccHandle hnd = new TCollection_HAsciiString(to_OccAsciiString(str)); - return hnd; + return makeOccHandle(to_OccAsciiString(str)); } }; @@ -179,8 +178,7 @@ template<> struct StringConv { // std::string -> Handle(TCollection_HAsciiString) template<> struct StringConv> { static auto to(const std::string& str) { - OccHandle hnd = new TCollection_HAsciiString(str.c_str()); - return hnd; + return makeOccHandle(str.c_str()); } }; diff --git a/src/cli/main.cpp b/src/cli/main.cpp index bc02e489..46a694c9 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -340,8 +340,8 @@ static void initGui(GuiApplication* guiApp) return; guiApp->setAutomaticDocumentMapping(false); // GuiDocument objects aren't needed - setFunctionCreateGraphicsDriver([]() -> OccHandle { - return new OpenGl_GraphicDriver(GraphicsUtils::AspectDisplayConnection_create()); + setFunctionCreateGraphicsDriver([]{ + return makeOccHandle(GraphicsUtils::AspectDisplayConnection_create()); }); guiApp->addGraphicsObjectDriver(std::make_unique()); guiApp->addGraphicsObjectDriver(std::make_unique()); diff --git a/src/graphics/graphics_create_driver.cpp b/src/graphics/graphics_create_driver.cpp index 17a29682..733e8c63 100644 --- a/src/graphics/graphics_create_driver.cpp +++ b/src/graphics/graphics_create_driver.cpp @@ -23,7 +23,7 @@ using FunctionCreateGraphicsDriver = std::function(new Aspect_DisplayConnection); }; return fn; } diff --git a/src/graphics/graphics_mesh_object_driver.cpp b/src/graphics/graphics_mesh_object_driver.cpp index 27ef4c4c..a7258d72 100644 --- a/src/graphics/graphics_mesh_object_driver.cpp +++ b/src/graphics/graphics_mesh_object_driver.cpp @@ -67,7 +67,7 @@ GraphicsObjectPtr GraphicsMeshObjectDriver::createObject(const TDF_Label& label) } if (polyTri) { - OccHandle object = new MeshVS_Mesh; + auto object = makeOccHandle(); object->SetDataSource(new GraphicsMeshDataSource(polyTri)); // meshVisu->AddBuilder(..., false); -> No selection if (!spanNodeColor.empty()) { diff --git a/src/graphics/graphics_scene.cpp b/src/graphics/graphics_scene.cpp index fc28bfd8..d1e575fb 100644 --- a/src/graphics/graphics_scene.cpp +++ b/src/graphics/graphics_scene.cpp @@ -25,7 +25,7 @@ namespace Internal { static OccHandle createOccViewer() { - OccHandle viewer = new V3d_Viewer(graphicsCreateDriver()); + auto viewer = makeOccHandle(graphicsCreateDriver()); viewer->SetDefaultViewSize(1000.); viewer->SetDefaultViewProj(V3d_XposYnegZpos); viewer->SetComputedMode(true); @@ -176,7 +176,7 @@ void GraphicsScene::redraw() this->signalRedrawRequested.send(itView.Value()); } -void GraphicsScene::redraw(const Handle_V3d_View& view) +void GraphicsScene::redraw(const OccHandle& view) { if (d->m_isRedrawBlocked) return; diff --git a/src/graphics/graphics_scene.h b/src/graphics/graphics_scene.h index 295acdcc..30c2a329 100644 --- a/src/graphics/graphics_scene.h +++ b/src/graphics/graphics_scene.h @@ -47,7 +47,7 @@ class GraphicsScene { void eraseObject(const GraphicsObjectPtr& object); void redraw(); - void redraw(const Handle_V3d_View& view); + void redraw(const OccHandle& view); bool isRedrawBlocked() const; void blockRedraw(bool on); @@ -106,7 +106,7 @@ class GraphicsScene { // Signals Signal<> signalSelectionChanged; Signal<> signalSelectionModeChanged; - Signal signalRedrawRequested; + Signal&> signalRedrawRequested; private: AIS_InteractiveContext* aisContextPtr() const; diff --git a/src/graphics/graphics_shape_object_driver.cpp b/src/graphics/graphics_shape_object_driver.cpp index 41e8d9c0..4111f5e5 100644 --- a/src/graphics/graphics_shape_object_driver.cpp +++ b/src/graphics/graphics_shape_object_driver.cpp @@ -51,7 +51,7 @@ GraphicsObjectPtr GraphicsShapeObjectDriver::createObject(const TDF_Label& label object->SetMaterial(Graphic3d_NOM_PLASTER); object->Attributes()->SetFaceBoundaryDraw(true); object->Attributes()->SetFaceBoundaryAspect( - new Prs3d_LineAspect(Quantity_NOC_BLACK, Aspect_TOL_SOLID, 1.) + new Prs3d_LineAspect(Quantity_NOC_BLACK, Aspect_TOL_SOLID, 1.) ); object->Attributes()->SetIsoOnTriangulation(true); //object->Attributes()->SetShadingModel(Graphic3d_TypeOfShadingModel_Pbr, true/*overrideDefaults*/); diff --git a/src/graphics/graphics_view_ptr.h b/src/graphics/graphics_view_ptr.h index c0400dd2..72b8a9e0 100644 --- a/src/graphics/graphics_view_ptr.h +++ b/src/graphics/graphics_view_ptr.h @@ -17,12 +17,12 @@ namespace Mayo { // a "redraw requested" signal class GraphicsViewPtr { public: - GraphicsViewPtr(GraphicsScene* scene, const Handle_V3d_View& view) + GraphicsViewPtr(GraphicsScene* scene, const OccHandle& view) : m_scene(scene), m_view(view) {} - const Handle_V3d_View& v3dView() const { + const OccHandle& v3dView() const { return m_view; } @@ -34,11 +34,11 @@ class GraphicsViewPtr { m_scene->redraw(m_view); } - const Handle_V3d_View& operator->() const { return m_view; } + const OccHandle& operator->() const { return m_view; } private: GraphicsScene* m_scene = nullptr; - Handle_V3d_View m_view; + OccHandle m_view; }; } // namespace Mayo diff --git a/src/gui/gui_document.cpp b/src/gui/gui_document.cpp index 66c0a37b..39fe3f48 100644 --- a/src/gui/gui_document.cpp +++ b/src/gui/gui_document.cpp @@ -34,8 +34,8 @@ namespace Internal { static OccHandle createOriginTrihedron() { - OccHandle axis = new Geom_Axis2Placement(gp::XOY()); - OccHandle aisTrihedron = new AIS_Trihedron(axis); + auto axis = makeOccHandle(gp::XOY()); + auto aisTrihedron = makeOccHandle(axis); aisTrihedron->SetDatumDisplayMode(Prs3d_DM_WireFrame); aisTrihedron->SetDrawArrows(false); aisTrihedron->Attributes()->DatumAspect()->LineAspect(Prs3d_DP_XAxis)->SetWidth(2.5); @@ -50,7 +50,7 @@ static OccHandle createOriginTrihedron() //aisTrihedron->SetTextColor(Quantity_NOC_GRAY40); aisTrihedron->SetSize(60); aisTrihedron->SetTransformPersistence( - new Graphic3d_TransformPers(Graphic3d_TMF_ZoomPers, axis->Ax2().Location()) + new Graphic3d_TransformPers(Graphic3d_TMF_ZoomPers, axis->Ax2().Location()) ); aisTrihedron->Attributes()->SetZLayer(Graphic3d_ZLayerId_Topmost); aisTrihedron->SetInfiniteState(true); @@ -90,13 +90,13 @@ GuiDocument::GuiDocument(const DocumentPtr& doc, GuiApplication* guiApp) m_v3dView->ChangeRenderingParams().NbMsaaSamples = 4; m_v3dView->ChangeRenderingParams().CollectedStats = Graphic3d_RenderingParams::PerfCounters_Extended; m_v3dView->ChangeRenderingParams().StatsPosition = new Graphic3d_TransformPers( - Graphic3d_TMF_2d, Aspect_TOTP_RIGHT_UPPER, Graphic3d_Vec2i(20, 20) + Graphic3d_TMF_2d, Aspect_TOTP_RIGHT_UPPER, Graphic3d_Vec2i(20, 20) ); // 3D view - Set gradient background m_v3dView->SetBgGradientColors( - GuiDocument::defaultGradientBackground().color1, - GuiDocument::defaultGradientBackground().color2, - GuiDocument::defaultGradientBackground().fillStyle + GuiDocument::defaultGradientBackground().color1, + GuiDocument::defaultGradientBackground().color2, + GuiDocument::defaultGradientBackground().fillStyle ); //m_v3dView->SetShadingModel(Graphic3d_TOSM_PBR); @@ -419,10 +419,11 @@ void GuiDocument::setViewTrihedronMode(ViewTrihedronMode mode) aisViewCube->SetFontHeight(12); aisViewCube->SetAxesLabels("", "", ""); aisViewCube->SetTransformPersistence( - new Graphic3d_TransformPers( - Graphic3d_TMF_TriedronPers, - m_viewTrihedronCorner, - Graphic3d_Vec2i(85, 85)) + new Graphic3d_TransformPers( + Graphic3d_TMF_TriedronPers, + m_viewTrihedronCorner, + Graphic3d_Vec2i(85, 85) + ) ); m_gfxScene.addObject(aisViewCube); //aisViewCube->Attributes()->DatumAspect()->LineAspect(Prs3d_DP_XAxis)->SetColor(Quantity_NOC_RED2); diff --git a/src/io_dxf/io_dxf.cpp b/src/io_dxf/io_dxf.cpp index be17feff..00e5c208 100644 --- a/src/io_dxf/io_dxf.cpp +++ b/src/io_dxf/io_dxf.cpp @@ -613,7 +613,7 @@ void DxfReader::Internal::OnReadMText(const Dxf_MTEXT& text) const gp_Ax3 locText(pt, extDir, xAxisDir); Font_BRepTextBuilder brepTextBuilder; #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) - OccHandle textFormat = new Font_TextFormatter; + auto textFormat = makeOccHandle(); textFormat->SetupAlignment(hAlign, vAlign); textFormat->Append(occTextStr, *brepFont.FTFont()); /* Font_TextFormatter computes weird ResultWidth() so wrapping is currently broken @@ -956,7 +956,7 @@ OccHandle DxfReader::Internal::createInterpolationSpline(cons const auto iNumPoints = CppUtils::safeStaticCast(spline.fitPoints.size()); // Handle poles - Handle_TColgp_HArray1OfPnt fitpoints = new TColgp_HArray1OfPnt(1, iNumPoints); + auto fitpoints = makeOccHandle(1, iNumPoints); for (const DxfCoords& pnt : spline.fitPoints) { const auto iPnt = CppUtils::safeStaticCast(&pnt - &spline.fitPoints.front()); fitpoints->ChangeValue(iPnt + 1) = gp_Pnt{pnt.x, pnt.y, pnt.z}; diff --git a/src/io_image/io_image.cpp b/src/io_image/io_image.cpp index 9bd63361..6d1d8dd8 100644 --- a/src/io_image/io_image.cpp +++ b/src/io_image/io_image.cpp @@ -186,7 +186,7 @@ OccHandle ImageWriter::createImage(GuiDocument* guiDoc, const OccHandle ImageWriter::createImage(OccHandle view) { - OccHandle pixmap = new Image_AlienPixMap; + auto pixmap = makeOccHandle(); V3d_ImageDumpOptions dumpOptions; dumpOptions.BufferType = Graphic3d_BT_RGB; view->Window()->Size(dumpOptions.Width, dumpOptions.Height); diff --git a/src/io_occ/io_occ_base_mesh.cpp b/src/io_occ/io_occ_base_mesh.cpp index 46f8ff8f..4241c679 100644 --- a/src/io_occ/io_occ_base_mesh.cpp +++ b/src/io_occ/io_occ_base_mesh.cpp @@ -77,7 +77,7 @@ TDF_LabelSequence OccBaseMeshReader::transfer(DocumentPtr doc, TaskProgress* pro this->applyParameters(); m_reader.SetDocument(doc); const TDF_LabelSequence seqMark = doc->xcaf().topLevelFreeShapes(); - OccHandle indicator = new OccProgressIndicator(progress); + auto indicator = makeOccHandle(progress); m_reader.Perform(m_filepath.u8string().c_str(), TKernelUtils::start(indicator)); return doc->xcaf().diffTopLevelFreeShapes(seqMark); } diff --git a/src/io_occ/io_occ_brep.cpp b/src/io_occ/io_occ_brep.cpp index 72630d9e..f05c3090 100644 --- a/src/io_occ/io_occ_brep.cpp +++ b/src/io_occ/io_occ_brep.cpp @@ -28,12 +28,13 @@ bool OccBRepReader::readFile(const FilePath& filepath, TaskProgress* progress) m_shape.Nullify(); m_baseFilename = filepath.stem(); BRep_Builder brepBuilder; - OccHandle indicator = new OccProgressIndicator(progress); + auto indicator = makeOccHandle(progress); return BRepTools::Read( - m_shape, - filepath.u8string().c_str(), - brepBuilder, - TKernelUtils::start(indicator)); + m_shape, + filepath.u8string().c_str(), + brepBuilder, + TKernelUtils::start(indicator) + ); } TDF_LabelSequence OccBRepReader::transfer(DocumentPtr doc, TaskProgress* /*progress*/) @@ -79,7 +80,7 @@ bool OccBRepWriter::transfer(Span appItems, TaskProgress* bool OccBRepWriter::writeFile(const FilePath& filepath, TaskProgress* progress) { - OccHandle indicator = new OccProgressIndicator(progress); + auto indicator = makeOccHandle(progress); return BRepTools::Write(m_shape, filepath.u8string().c_str(), TKernelUtils::start(indicator)); } diff --git a/src/io_occ/io_occ_caf.cpp b/src/io_occ/io_occ_caf.cpp index a78d618a..13d0a810 100644 --- a/src/io_occ/io_occ_caf.cpp +++ b/src/io_occ/io_occ_caf.cpp @@ -35,7 +35,7 @@ bool cafGenericReadFile(CafReaderType& reader, const FilePath& filepath, TaskPro template TDF_LabelSequence cafGenericReadTransfer(CafReaderType& reader, DocumentPtr doc, TaskProgress* progress) { - OccHandle indicator = new OccProgressIndicator(progress); + auto indicator = makeOccHandle(progress); const TDF_LabelSequence seqMark = doc->xcaf().topLevelFreeShapes(); OccHandle stdDoc = doc; #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) @@ -53,7 +53,7 @@ TDF_LabelSequence cafGenericReadTransfer(CafReaderType& reader, DocumentPtr doc, template bool cafGenericWriteTransfer(CafWriterType& writer, Span appItems, TaskProgress* progress) { - OccHandle indicator = new OccProgressIndicator(progress); + auto indicator = makeOccHandle(progress); #if OCC_VERSION_HEX < OCC_VERSION_CHECK(7, 5, 0) Private::cafFinderProcess(writer)->SetProgress(indicator); auto _ = gsl::finally([&]{ Private::cafFinderProcess(writer)->SetProgress(nullptr); }); diff --git a/src/io_occ/io_occ_gltf_writer.cpp b/src/io_occ/io_occ_gltf_writer.cpp index 03cbb836..9964442c 100644 --- a/src/io_occ/io_occ_gltf_writer.cpp +++ b/src/io_occ/io_occ_gltf_writer.cpp @@ -144,7 +144,7 @@ bool OccGltfWriter::writeFile(const FilePath& filepath, TaskProgress* progress) if (!m_document) return false; - OccHandle occProgress = new OccProgressIndicator(progress); + auto occProgress = makeOccHandle(progress); const bool isBinary = m_params.format == Format::Binary; RWGltf_CafWriter writer(filepath.u8string().c_str(), isBinary); writer.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(m_params.inputCoordinateSystem); diff --git a/src/io_occ/io_occ_obj_writer.cpp b/src/io_occ/io_occ_obj_writer.cpp index 4bebcdbf..f5762a13 100644 --- a/src/io_occ/io_occ_obj_writer.cpp +++ b/src/io_occ/io_occ_obj_writer.cpp @@ -68,7 +68,7 @@ bool OccObjWriter::writeFile(const FilePath& filepath, TaskProgress* progress) if (!m_document) return false; - OccHandle occProgress = new OccProgressIndicator(progress); + auto occProgress = makeOccHandle(progress); RWObj_CafWriter writer(filepath.u8string().c_str()); writer.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(m_params.inputCoordinateSystem); writer.ChangeCoordinateSystemConverter().SetOutputCoordinateSystem(m_params.outputCoordinateSystem); diff --git a/src/io_occ/io_occ_stl.cpp b/src/io_occ/io_occ_stl.cpp index ab661cd1..415aa971 100644 --- a/src/io_occ/io_occ_stl.cpp +++ b/src/io_occ/io_occ_stl.cpp @@ -73,7 +73,7 @@ class OccStlWriter::Properties : public PropertyGroup { bool OccStlReader::readFile(const FilePath& filepath, TaskProgress* progress) { - OccHandle indicator = new OccProgressIndicator(progress); + auto indicator = makeOccHandle(progress); m_baseFilename = filepath.stem(); m_mesh = RWStl::ReadFile(filepath.u8string().c_str(), TKernelUtils::start(indicator)); return !m_mesh.IsNull(); @@ -131,7 +131,7 @@ bool OccStlWriter::writeFile(const FilePath& filepath, TaskProgress* progress) writer.ASCIIMode() = m_params.format == Format::Ascii; const std::string strFilepath = filepath.u8string(); #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) - OccHandle indicator = new OccProgressIndicator(progress); + auto indicator = makeOccHandle(progress); return writer.Write(m_shape, strFilepath.c_str(), TKernelUtils::start(indicator)); #else MAYO_UNUSED(progress); diff --git a/src/io_off/io_off_reader.cpp b/src/io_off/io_off_reader.cpp index 2318d004..e3f0e667 100644 --- a/src/io_off/io_off_reader.cpp +++ b/src/io_off/io_off_reader.cpp @@ -315,7 +315,7 @@ TDF_Label OffReader::transferMesh(DocumentPtr doc, TaskProgress* progress) triangleCount += facet.vertexCount - 2; // Create mesh object - OccHandle mesh = new Poly_Triangulation(vertexCount, triangleCount, false/*!hasUvNodes*/); + auto mesh = makeOccHandle(vertexCount, triangleCount, false/*!hasUvNodes*/); // Helper function for progress report auto fnUpdateProgress = [=](int current) { diff --git a/src/io_ply/io_ply_reader.cpp b/src/io_ply/io_ply_reader.cpp index c2662c02..c28e4d85 100644 --- a/src/io_ply/io_ply_reader.cpp +++ b/src/io_ply/io_ply_reader.cpp @@ -155,7 +155,7 @@ TDF_Label PlyReader::transferMesh(DocumentPtr doc, TaskProgress* /*progress*/) { // Create target mesh const int triangleCount = CppUtils::safeStaticCast(m_vecIndex.size() / 3); - OccHandle mesh = new Poly_Triangulation(m_nodeCount, triangleCount, false/*hasUvNodes*/); + auto mesh = makeOccHandle(m_nodeCount, triangleCount, false/*hasUvNodes*/); if (!m_vecNormalCoord.empty()) MeshUtils::allocateNormals(mesh); diff --git a/src/measure/measure_display.cpp b/src/measure/measure_display.cpp index 2ae9a285..f5fe1503 100644 --- a/src/measure/measure_display.cpp +++ b/src/measure/measure_display.cpp @@ -68,16 +68,16 @@ std::unique_ptr BaseMeasureDisplay::createEmptySumFrom(MeasureT } } -void BaseMeasureDisplay::adaptGraphics(const Handle_Graphic3d_GraphicDriver& driver) +void BaseMeasureDisplay::adaptGraphics(const OccHandle& driver) { - const auto openGlDriver = Handle_OpenGl_GraphicDriver::DownCast(driver); + const auto openGlDriver = OccHandle::DownCast(driver); const auto openGlContext = openGlDriver ? openGlDriver->GetSharedContext() : nullptr; if (!openGlContext) return; const bool useVbo = openGlContext->ToUseVbo(); for (int i = 0; i < this->graphicsObjectsCount(); ++i) { - auto gfxText = Handle(AIS_TextLabel)::DownCast(this->graphicsObjectAt(i)); + auto gfxText = OccHandle::DownCast(this->graphicsObjectAt(i)); if (gfxText) { // NOTE // Usage of Aspect_TODT_SUBTITLE is causing a crash when VBO are not available(eg because @@ -343,7 +343,7 @@ MeasureDisplayAngle::MeasureDisplayAngle(MeasureAngle angle) const gp_Vec vec1(angle.pntCenter, angle.pnt1); const gp_Vec vec2(angle.pntCenter, angle.pnt2); const gp_Ax2 axCircle(angle.pntCenter, vec1.Crossed(vec2), vec1); - OccHandle geomCircle = new Geom_Circle(axCircle, 0.8 * vec1.Magnitude()); + auto geomCircle = makeOccHandle(axCircle, 0.8 * vec1.Magnitude()); const double param1 = ElCLib::Parameter(geomCircle->Circ(), angle.pnt1); const double param2 = ElCLib::Parameter(geomCircle->Circ(), angle.pnt2); m_gfxAngle = new AIS_Circle(geomCircle, param1, param2); diff --git a/src/measure/measure_display.h b/src/measure/measure_display.h index 8c545726..db252b50 100644 --- a/src/measure/measure_display.h +++ b/src/measure/measure_display.h @@ -55,7 +55,7 @@ class IMeasureDisplay { // Adapt 3D graphics objects to what is supported by 'driver' // This function must be called before adding the graphical objects to the 3D scene(Mayo::GraphicsScene // or AIS_InteractiveContext) - virtual void adaptGraphics(const Handle_Graphic3d_GraphicDriver& driver) = 0; + virtual void adaptGraphics(const OccHandle& driver) = 0; // Whether "sum" mode is supported by the measure display // This is relevant when multiple measureable 3D objects are selected. The cumulative sum of @@ -76,7 +76,7 @@ class BaseMeasureDisplay : public IMeasureDisplay { static std::unique_ptr createFrom(MeasureType type, const MeasureValue& value); static std::unique_ptr createEmptySumFrom(MeasureType type); - void adaptGraphics(const Handle_Graphic3d_GraphicDriver& driver) override; + void adaptGraphics(const OccHandle& driver) override; bool isSumSupported() const override { return false; } void sumAdd(const IMeasureDisplay& other) override; @@ -207,7 +207,7 @@ class MeasureDisplayLength : public BaseMeasureDisplay { private: MeasureLength m_length; - Handle_AIS_TextLabel m_gfxLenText; + OccHandle m_gfxLenText; }; // -- @@ -226,7 +226,7 @@ class MeasureDisplayArea : public BaseMeasureDisplay { private: MeasureArea m_area; - Handle_AIS_TextLabel m_gfxAreaText; + OccHandle m_gfxAreaText; }; // -- @@ -242,12 +242,12 @@ class MeasureDisplayBoundingBox : public BaseMeasureDisplay { private: MeasureBoundingBox m_bnd; - Handle_AIS_Point m_gfxMinPoint; - Handle_AIS_Point m_gfxMaxPoint; - Handle_AIS_InteractiveObject m_gfxBox; - Handle_AIS_TextLabel m_gfxXLengthText; - Handle_AIS_TextLabel m_gfxYLengthText; - Handle_AIS_TextLabel m_gfxZLengthText; + OccHandle m_gfxMinPoint; + OccHandle m_gfxMaxPoint; + OccHandle m_gfxBox; + OccHandle m_gfxXLengthText; + OccHandle m_gfxYLengthText; + OccHandle m_gfxZLengthText; }; } // namespace Mayo diff --git a/src/measure/measure_tool_brep.cpp b/src/measure/measure_tool_brep.cpp index b53dbbb8..0b5c5a13 100644 --- a/src/measure/measure_tool_brep.cpp +++ b/src/measure/measure_tool_brep.cpp @@ -513,7 +513,7 @@ MeasureArea MeasureToolBRep::brepArea(const TopoDS_Shape& shape) } else { TopLoc_Location loc; - const Handle_Poly_Triangulation& triangulation = BRep_Tool::Triangulation(face, loc); + const OccHandle& triangulation = BRep_Tool::Triangulation(face, loc); throwErrorIf(triangulation.IsNull()); areaResult.value = MeshUtils::triangulationArea(triangulation) * Quantity_SquareMillimeter; diff --git a/src/qtcommon/qstring_conv.h b/src/qtcommon/qstring_conv.h index 3bcf4d17..dc606509 100644 --- a/src/qtcommon/qstring_conv.h +++ b/src/qtcommon/qstring_conv.h @@ -128,8 +128,7 @@ template<> struct StringConv { // QString -> Handle(TCollection_HAsciiString) template<> struct StringConv> { static auto to(const QString& str) { - OccHandle hnd = new TCollection_HAsciiString(qUtf8Printable(str)); - return hnd; + return makeOccHandle(qUtf8Printable(str)); } }; diff --git a/tests/test_app.cpp b/tests/test_app.cpp index 0fae5e22..71933659 100644 --- a/tests/test_app.cpp +++ b/tests/test_app.cpp @@ -261,7 +261,7 @@ void TestApp::StringConv_test() { const QString text = "test_éç²µ§_测试_Тест"; QCOMPARE(to_QString(string_conv(text)), text); - QCOMPARE(to_QString(string_conv(text)), text); + QCOMPARE(to_QString(to_OccHandleHAsciiString(text)), text); QCOMPARE(to_QString(to_stdString(text)), text); QCOMPARE(to_QString(to_OccExtString(text)), text); } diff --git a/tests/test_base.cpp b/tests/test_base.cpp index ec9b3834..eb6fb229 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -893,7 +893,7 @@ void TestBase::MeshUtils_test() }); // Merge all face triangulations into one - OccHandle polyTriBox = new Poly_Triangulation(countNode, countTriangle, false); + auto polyTriBox = makeOccHandle(countNode, countTriangle, false); { int idNodeOffset = 0; int idTriangleOffset = 0; From 99333800237e3a065954108447d621aaae7268a7 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Tue, 7 May 2024 15:58:02 +0200 Subject: [PATCH 068/101] Base: make Application a regular class(not singleton) --- src/app/app_module.cpp | 4 ++- src/app/app_module.h | 5 +++ src/app/commands_file.cpp | 2 +- src/app/main.cpp | 6 ++-- src/base/application.cpp | 68 +++++++++++--------------------------- src/base/application.h | 20 +++-------- src/base/application_ptr.h | 4 +-- src/base/text_id.cpp | 34 +++++++++++++++++-- src/base/text_id.h | 9 +++++ src/cli/cli_export.cpp | 5 ++- src/cli/cli_export.h | 5 ++- src/cli/main.cpp | 4 +-- tests/test_app.cpp | 6 +--- tests/test_base.cpp | 13 ++++---- tests/test_measure.cpp | 5 +-- 15 files changed, 99 insertions(+), 91 deletions(-) diff --git a/src/app/app_module.cpp b/src/app/app_module.cpp index 602d6767..abc58978 100644 --- a/src/app/app_module.cpp +++ b/src/app/app_module.cpp @@ -82,7 +82,8 @@ QuantityLength shapeChordalDeflection(const TopoDS_Shape& shape) } // namespace AppModule::AppModule() - : m_settings(new Settings), + : m_application(new Application), + m_settings(new Settings), m_props(m_settings), m_stdLocale(std::locale("")), m_qtLocale(QLocale::system()) @@ -94,6 +95,7 @@ AppModule::AppModule() } m_settings->setPropertyValueConversion(this); + Application::defineMayoFormat(m_application); } QStringUtils::TextOptions AppModule::defaultTextOptions() const diff --git a/src/app/app_module.h b/src/app/app_module.h index 2fc4c1b8..56a57fbe 100644 --- a/src/app/app_module.h +++ b/src/app/app_module.h @@ -9,6 +9,7 @@ #include "app_module_properties.h" #include "qstring_utils.h" +#include "../base/application.h" #include "../base/document_tree_node_properties_provider.h" #include "../base/io_parameters_provider.h" #include "../base/io_system.h" @@ -53,6 +54,9 @@ class AppModule : ~AppModule(); + // Application object + const ApplicationPtr& application() const { return m_application; } + // Settings const AppModuleProperties* properties() const { return &m_props; } AppModuleProperties* properties() { return &m_props; } @@ -121,6 +125,7 @@ class AppModule : bool impl_recordRecentFile(RecentFile* recentFile, GuiDocument* guiDoc); + ApplicationPtr m_application; Settings* m_settings = nullptr; IO::System m_ioSystem; AppModuleProperties m_props; diff --git a/src/app/commands_file.cpp b/src/app/commands_file.cpp index 8c9d00b8..e382774c 100644 --- a/src/app/commands_file.cpp +++ b/src/app/commands_file.cpp @@ -229,7 +229,7 @@ void FileCommandTools::importInDocument( QElapsedTimer chrono; chrono.start(); - auto doc = Application::instance()->findDocumentByIdentifier(targetDocId); + auto doc = appModule->application()->findDocumentByIdentifier(targetDocId); const bool okImport = appModule->ioSystem()->importInDocument() .targetDocument(doc) .withFilepaths(listFilePaths) diff --git a/src/app/main.cpp b/src/app/main.cpp index c06e0ce2..450f90af 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -368,9 +368,11 @@ static int runApp(QCoreApplication* qtApp) } // Initialize Base application - auto app = Application::instance().get(); - app->addTranslator(&qtAppTranslate); // Set Qt i18n backend + auto app = appModule->application(); + TextId::addTranslatorFunction(&qtAppTranslate); // Set Qt i18n backend +#ifdef MAYO_OS_WINDOWS initOpenCascadeEnvironment("opencascade.conf"); +#endif // Initialize Gui application auto guiApp = new GuiApplication(app); diff --git a/src/base/application.cpp b/src/base/application.cpp index ab953787..bf82e5f4 100644 --- a/src/base/application.cpp +++ b/src/base/application.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include #if OCC_VERSION_HEX < OCC_VERSION_CHECK(7, 5, 0) @@ -20,7 +19,6 @@ #endif #include -#include #include namespace Mayo { @@ -52,37 +50,20 @@ class Document::FormatXmlRetrievalDriver : public XmlXCAFDrivers_DocumentRetriev struct Application::Private { std::atomic m_seqDocumentIdentifier = {}; std::unordered_map m_mapIdentifierDocument; - std::vector m_vecTranslator; }; struct ApplicationI18N { MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::Application) }; -Application::~Application() +Application::Application() + : d(new Private) { - delete d; } -const ApplicationPtr& Application::instance() +Application::~Application() { - static ApplicationPtr appPtr; - if (!appPtr) { - appPtr = new Application; - const char strFougueCopyright[] = "Copyright (c) 2021, Fougue Ltd. "; - appPtr->DefineFormat( - Document::NameFormatBinary, ApplicationI18N::textIdTr("Binary Mayo Document Format").data(), "myb", - new Document::FormatBinaryRetrievalDriver(appPtr), - new BinXCAFDrivers_DocumentStorageDriver - ); - appPtr->DefineFormat( - Document::NameFormatXml, ApplicationI18N::textIdTr("XML Mayo Document Format").data(), "myx", - new Document::FormatXmlRetrievalDriver(appPtr), - new XmlXCAFDrivers_DocumentStorageDriver(strFougueCopyright) - ); - } - - return appPtr; + delete d; } int Application::documentCount() const @@ -117,7 +98,7 @@ DocumentPtr Application::openDocument(const FilePath& filepath, PCDM_ReaderStatu DocumentPtr Application::findDocumentByIndex(int docIndex) const { OccHandle doc; - TDocStd_Application::GetDocument(docIndex + 1, doc); + XCAFApp_Application::GetDocument(docIndex + 1, doc); return !doc.IsNull() ? DocumentPtr::DownCast(doc) : DocumentPtr(); } @@ -150,7 +131,7 @@ int Application::findIndexOfDocument(const DocumentPtr& doc) const void Application::closeDocument(const DocumentPtr& doc) { - TDocStd_Application::Close(doc); + XCAFApp_Application::Close(doc); doc->signalNameChanged.disconnectAll(); doc->signalFilePathChanged.disconnectAll(); doc->signalEntityAdded.disconnectAll(); @@ -158,22 +139,19 @@ void Application::closeDocument(const DocumentPtr& doc) //doc->Main().ForgetAllAttributes(true/*clearChildren*/); } -void Application::addTranslator(Application::Translator fn) -{ - if (fn) - d->m_vecTranslator.push_back(std::move(fn)); -} - -std::string_view Application::translate(const TextId& textId, int n) const +void Application::defineMayoFormat(const ApplicationPtr& app) { - for (auto it = d->m_vecTranslator.rbegin(); it != d->m_vecTranslator.rend(); ++it) { - const Application::Translator& fn = *it; - std::string_view msg = fn(textId, n); - if (!msg.empty()) - return msg; - } - - return textId.key; + const char strFougueCopyright[] = "Copyright (c) 2024, Fougue Ltd. "; + app->DefineFormat( + Document::NameFormatBinary, ApplicationI18N::textIdTr("Binary Mayo Document Format").data(), "myb", + new Document::FormatBinaryRetrievalDriver(app), + new BinXCAFDrivers_DocumentStorageDriver + ); + app->DefineFormat( + Document::NameFormatXml, ApplicationI18N::textIdTr("XML Mayo Document Format").data(), "myx", + new Document::FormatXmlRetrievalDriver(app), + new XmlXCAFDrivers_DocumentStorageDriver(strFougueCopyright) + ); } Span Application::envOpenCascadeOptions() @@ -215,7 +193,7 @@ void Application::NewDocument(const TCollection_ExtendedString&, OccHandle& doc) const void Application::InitDocument(const OccHandle& doc) const #endif { - TDocStd_Application::InitDocument(doc); - XCAFApp_Application::GetApplication()->InitDocument(doc); -} - -Application::Application() - : d(new Private) -{ + XCAFApp_Application::InitDocument(doc); } void Application::notifyDocumentAboutToClose(Document::Identifier docIdent) diff --git a/src/base/application.h b/src/base/application.h index d733ce04..746433e5 100644 --- a/src/base/application.h +++ b/src/base/application.h @@ -11,22 +11,19 @@ #include "occ_handle.h" #include "signal.h" #include "span.h" -#include "text_id.h" #include #include -#include +#include namespace Mayo { // Provides management of Document objects -class Application : public TDocStd_Application { +class Application : public XCAFApp_Application { public: + Application(); ~Application(); - // Global instance(singleton) - static const ApplicationPtr& instance(); - // Iterator over Documents contained in an Application struct DocumentIterator : private CDF_DirectoryIterator { DocumentIterator(const ApplicationPtr& app); @@ -49,13 +46,7 @@ class Application : public TDocStd_Application { void closeDocument(const DocumentPtr& doc); - // Provides internationalization support for text output - // 1st arg: message to be translated(TextId = context+key) - // 2nd arg: when != -1 used to choose an appropriate form for the translation(e.g. "%n file found" vs. "%n files found") - // returns: translated message - using Translator = std::function; - void addTranslator(Translator fn); - std::string_view translate(const TextId& textId, int n = -1) const; + static void defineMayoFormat(const ApplicationPtr& app); static Span envOpenCascadeOptions(); static Span envOpenCascadePaths(); @@ -83,12 +74,11 @@ class Application : public TDocStd_Application { // -> Can't do because PCDM_RetrievalDriver subclasses create explicitly "new TDocStd_Document(...)" // This would break TDocStd_Application::Open(...) - DEFINE_STANDARD_RTTI_INLINE(Application, TDocStd_Application) + DEFINE_STANDARD_RTTI_INLINE(Application, XCAFApp_Application) private: // Implementation friend class Document; - Application(); void notifyDocumentAboutToClose(Document::Identifier docIdent); void addDocument(const DocumentPtr& doc); diff --git a/src/base/application_ptr.h b/src/base/application_ptr.h index a8cbbc2a..bab06e0f 100644 --- a/src/base/application_ptr.h +++ b/src/base/application_ptr.h @@ -7,12 +7,12 @@ #pragma once #include "occ_handle.h" -#include +#include namespace Mayo { class Application; -DEFINE_STANDARD_HANDLE(Application, TDocStd_Application) +DEFINE_STANDARD_HANDLE(Application, XCAFApp_Application) using ApplicationPtr = OccHandle; } // namespace Mayo diff --git a/src/base/text_id.cpp b/src/base/text_id.cpp index c821ea4f..a8847bf9 100644 --- a/src/base/text_id.cpp +++ b/src/base/text_id.cpp @@ -5,13 +5,24 @@ ****************************************************************************/ #include "text_id.h" -#include "application.h" + +#include namespace Mayo { +namespace { + +std::vector& getTranslatorFunctions() +{ + static std::vector vec; + return vec; +} + +} // namespace + std::string_view TextId::tr(int n) const { - return Application::instance()->translate(*this, n); + return TextId::translate(*this, n); } bool TextId::isEmpty() const @@ -19,4 +30,23 @@ bool TextId::isEmpty() const return this->key.empty(); } +void TextId::addTranslatorFunction(TranslatorFunctionPtr fn) +{ + if (fn) + getTranslatorFunctions().push_back(fn); +} + +std::string_view TextId::translate(const TextId& textId, int n) +{ + for (auto it = getTranslatorFunctions().rbegin(); it != getTranslatorFunctions().rend(); ++it) { + TranslatorFunctionPtr fn = *it; + std::string_view msg = fn(textId, n); + if (!msg.empty()) + return msg; + } + + return textId.key; +} + + } // namespace Mayo diff --git a/src/base/text_id.h b/src/base/text_id.h index 7e19effd..9e5d2dbb 100644 --- a/src/base/text_id.h +++ b/src/base/text_id.h @@ -37,6 +37,15 @@ struct TextId { // Whether source text(key) is empty or not bool isEmpty() const; + + // Provides internationalization support for text output + // 1st arg: message to be translated(TextId = context+key) + // 2nd arg: when != -1 used to choose an appropriate form for the translation(e.g. "%n file found" vs. "%n files found") + // returns: translated message + using TranslatorFunction = std::string_view(const TextId&, int); + using TranslatorFunctionPtr = TranslatorFunction*; + static void addTranslatorFunction(TranslatorFunctionPtr fn); + static std::string_view translate(const TextId& textId, int n = -1); }; } // namespace Mayo diff --git a/src/cli/cli_export.cpp b/src/cli/cli_export.cpp index 989267cc..2437a1a3 100644 --- a/src/cli/cli_export.cpp +++ b/src/cli/cli_export.cpp @@ -166,7 +166,10 @@ void exportDocument(const DocumentPtr& doc, const FilePath& filepath, Helper* he } // namespace void cli_asyncExportDocuments( - Application* app, const CliExportArgs& args, std::function fnContinuation) + const ApplicationPtr& app, + const CliExportArgs& args, + std::function fnContinuation + ) { auto helper = new Helper; // Allocated on heap because current function is asynchronous auto taskMgr = &helper->taskMgr; diff --git a/src/cli/cli_export.h b/src/cli/cli_export.h index e57944b6..04e3ce9b 100644 --- a/src/cli/cli_export.h +++ b/src/cli/cli_export.h @@ -6,6 +6,7 @@ #pragma once +#include "../base/application_ptr.h" #include "../base/filepath.h" #include "../base/span.h" @@ -13,8 +14,6 @@ namespace Mayo { -class Application; - // Contains arguments for the cli_asyncExportDocuments() function struct CliExportArgs { bool progressReport = true; @@ -25,7 +24,7 @@ struct CliExportArgs { // Asynchronously exports input file(s) listed in 'args' // Calls 'fnContinuation' at the end of execution void cli_asyncExportDocuments( - Application* app, + const ApplicationPtr& app, const CliExportArgs& args, std::function fnContinuation ); diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 46a694c9..e3512d2f 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -405,8 +405,8 @@ static int runApp(QCoreApplication* qtApp) } // Initialize Base application - auto app = Application::instance().get(); - app->addTranslator(&qtAppTranslate); // Set Qt i18n backend + auto app = appModule->application(); + TextId::addTranslatorFunction(&qtAppTranslate); // Set Qt i18n backend #ifdef MAYO_OS_WINDOWS initOpenCascadeEnvironment("opencascade.conf"); #endif diff --git a/tests/test_app.cpp b/tests/test_app.cpp index 71933659..0db98011 100644 --- a/tests/test_app.cpp +++ b/tests/test_app.cpp @@ -66,7 +66,7 @@ RecentFile createRecentFile(const QPixmap& thumbnail) void TestApp::DocumentFilesWatcher_test() { - auto app = Application::instance(); + auto app = makeOccHandle(); DocumentFilesWatcher docFilesWatcher(app); docFilesWatcher.enable(true); @@ -87,10 +87,6 @@ void TestApp::DocumentFilesWatcher_test() fnCopyCadFile(); DocumentPtr doc = app->newDocument(); doc->setFilePath(cadFilePath); - auto _ = gsl::finally([=]{ - if (app->findIndexOfDocument(doc) != -1) - app->closeDocument(doc); - }); // Check file change on document is caught fnCopyCadFile(); diff --git a/tests/test_base.cpp b/tests/test_base.cpp index eb6fb229..d4ab9c61 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -188,7 +188,7 @@ struct SignalEmitSpy { void TestBase::Application_test() { - auto app = Application::instance(); + auto app = makeOccHandle(); auto fnImportInDocument = [=](const DocumentPtr& doc, const FilePath& fp) { return m_ioSystem->importInDocument() .targetDocument(doc) @@ -259,9 +259,10 @@ void TestBase::Application_test() void TestBase::DocumentRefCount_test() { - DocumentPtr doc = Application::instance()->newDocument(); + auto app = makeOccHandle(); + DocumentPtr doc = app->newDocument(); QVERIFY(doc->GetRefCount() > 1); - Application::instance()->closeDocument(doc); + app->closeDocument(doc); QCOMPARE(doc->GetRefCount(), 1); } @@ -609,7 +610,7 @@ void TestBase::IO_bugGitHub166_test() QFETCH(QString, strOutputFilePath); QFETCH(IO::Format, outputFormat); - auto app = Application::instance(); + auto app = makeOccHandle(); DocumentPtr doc = app->newDocument(); const bool okImport = m_ioSystem->importInDocument() .targetDocument(doc) @@ -665,7 +666,7 @@ void TestBase::IO_bugGitHub166_test_data() void TestBase::IO_bugGitHub258_test() { - auto app = Application::instance(); + auto app = makeOccHandle(); DocumentPtr doc = app->newDocument(); const bool okImport = m_ioSystem->importInDocument() .targetDocument(doc) @@ -681,8 +682,6 @@ void TestBase::IO_bugGitHub258_test() QVERIFY(!triangulation.IsNull()); QCOMPARE(triangulation->NbNodes(), 24); QCOMPARE(triangulation->NbTriangles(), 12); - - app->closeDocument(doc); } void TestBase::DoubleToString_test() diff --git a/tests/test_measure.cpp b/tests/test_measure.cpp index 061f93e3..d8f6ea63 100644 --- a/tests/test_measure.cpp +++ b/tests/test_measure.cpp @@ -182,7 +182,8 @@ void TestMeasure::BRepArea_TriangulationFace_test() const bool okRead = reader.readFile("tests/inputs/face_trsf_scale_almost_1.stl", progress); QVERIFY(okRead); - auto doc = Application::instance()->newDocument(); + auto app = makeOccHandle(); + auto doc = app->newDocument(); const TDF_LabelSequence seqLabel = reader.transfer(doc, progress); QCOMPARE(seqLabel.Size(), 1); const TopoDS_Shape shape = doc->xcaf().shape(seqLabel.First()); @@ -193,7 +194,7 @@ void TestMeasure::BRepArea_TriangulationFace_test() void TestMeasure::BRepBoundingBox_Sphere_test() { const double sphereRadius = 50.; - const TopoDS_Shape sphereShape = BRepPrimAPI_MakeSphere(50.); + const TopoDS_Shape sphereShape = BRepPrimAPI_MakeSphere(sphereRadius); const MeasureBoundingBox bndBox = MeasureToolBRep::brepBoundingBox(sphereShape); QVERIFY(bndBox.cornerMin.IsEqual(gp_Pnt{-sphereRadius, -sphereRadius, -sphereRadius}, Precision::Confusion())); QVERIFY(bndBox.cornerMax.IsEqual(gp_Pnt{sphereRadius, sphereRadius, sphereRadius}, Precision::Confusion())); From 63c37fd64fda95420889fb905c9ee10b05fafce7 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 17 May 2024 12:55:37 +0200 Subject: [PATCH 069/101] Base: add MeshUtils::normal() helper function --- src/base/mesh_utils.cpp | 14 ++++++++++++++ src/base/mesh_utils.h | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/base/mesh_utils.cpp b/src/base/mesh_utils.cpp index 621bd78f..d573229a 100644 --- a/src/base/mesh_utils.cpp +++ b/src/base/mesh_utils.cpp @@ -135,6 +135,20 @@ void allocateNormals(const OccHandle& triangulation) #endif } +Poly_Triangulation_NormalType normal(const OccHandle& triangulation, int index) +{ + Poly_Triangulation_NormalType nvec; +#if OCC_VERSION_HEX >= 0x070600 + triangulation->Normal(index, nvec); +#else + const TShort_Array1OfShortReal& normals = triangulation->Normals(); + nvec.SetX(normals.Value(index * 3 - 2)); + nvec.SetY(normals.Value(index * 3 - 1)); + nvec.SetZ(normals.Value(index * 3)); +#endif + return nvec; +} + const Poly_Array1OfTriangle& triangles(const OccHandle& triangulation) { #if OCC_VERSION_HEX < 0x070600 diff --git a/src/base/mesh_utils.h b/src/base/mesh_utils.h index 08fa0a15..1730ca11 100644 --- a/src/base/mesh_utils.h +++ b/src/base/mesh_utils.h @@ -31,11 +31,13 @@ using Poly_Triangulation_NormalType = gp_Vec; #endif void setNode(const OccHandle& triangulation, int index, const gp_Pnt& pnt); -void setTriangle(const OccHandle& triangulation, int index, const Poly_Triangle& triangle); void setNormal(const OccHandle& triangulation, int index, const Poly_Triangulation_NormalType& n); +void setTriangle(const OccHandle& triangulation, int index, const Poly_Triangle& triangle); void setUvNode(const OccHandle& triangulation, int index, double u, double v); + void allocateNormals(const OccHandle& triangulation); +Poly_Triangulation_NormalType normal(const OccHandle& triangulation, int index); const Poly_Array1OfTriangle& triangles(const OccHandle& triangulation); enum class Orientation { From 61f1455129602c2fe107ed17bb21b9d0af6c14f5 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Fri, 17 May 2024 12:56:08 +0200 Subject: [PATCH 070/101] IO_Assimp: fix handling of node scale --- src/base/occ_handle.h | 16 +- src/base/tkernel_utils.h | 15 -- src/io_assimp/io_assimp_reader.cpp | 230 +++++++++++++++++++---------- src/io_assimp/io_assimp_reader.h | 4 - 4 files changed, 171 insertions(+), 94 deletions(-) diff --git a/src/base/occ_handle.h b/src/base/occ_handle.h index 1a4decbf..290bbc36 100644 --- a/src/base/occ_handle.h +++ b/src/base/occ_handle.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include // For std::forward() namespace Mayo { @@ -14,7 +15,7 @@ namespace Mayo { // Template alias for OpenCascade handle template using OccHandle = opencascade::handle; -// Constructs an object of 'T' wrapped in an OpenCascade handle +// Constructs an object of type 'T' wrapped in an OpenCascade handle // Note: Standard_Transient must be a base class of 'T' template OccHandle makeOccHandle(Args&&... args) @@ -23,3 +24,16 @@ OccHandle makeOccHandle(Args&&... args) } } // namespace Mayo + +#if OCC_VERSION_HEX < 0x070800 +namespace std { + +// Specialization of C++11 std::hash<> functor for opencascade::handle<> objects +template struct hash> { + inline std::size_t operator()(const opencascade::handle& hnd) const { + return hash{}(hnd.get()); + } +}; + +} // namespace std +#endif diff --git a/src/base/tkernel_utils.h b/src/base/tkernel_utils.h index 55678134..6be4fa04 100644 --- a/src/base/tkernel_utils.h +++ b/src/base/tkernel_utils.h @@ -9,7 +9,6 @@ #include "occ_handle.h" #include -#include #include #include #include @@ -21,7 +20,6 @@ #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) # include #else -# include class Message_ProgressIndicator; #endif @@ -56,16 +54,3 @@ class TKernelUtils { }; } // namespace Mayo - -#if OCC_VERSION_HEX < OCC_VERSION_CHECK(7, 8, 0) -namespace std { - -// Specialization of C++11 std::hash<> functor for opencascade::handle<> objects -template struct hash> { - inline std::size_t operator()(const opencascade::handle& hnd) const { - return hash{}(hnd.get()); - } -}; - -} // namespace std -#endif diff --git a/src/io_assimp/io_assimp_reader.cpp b/src/io_assimp/io_assimp_reader.cpp index 7b61b7f8..4f9a5b3d 100644 --- a/src/io_assimp/io_assimp_reader.cpp +++ b/src/io_assimp/io_assimp_reader.cpp @@ -17,6 +17,7 @@ #include "../base/property.h" #include "../base/string_conv.h" #include "../base/task_progress.h" +#include "../base/tkernel_utils.h" #include "../base/xcaf.h" #include @@ -29,6 +30,7 @@ #include #include +#include #include #include #include @@ -52,21 +54,11 @@ struct AssimpReaderI18N { // Retrieve the scaling component in assimp matrix 'trsf' aiVector3D aiMatrixScaling(const aiMatrix4x4& trsf) { - const ai_real scalingX = aiVector3D(trsf.a1, trsf.a2, trsf.a3).Length(); - const ai_real scalingY = aiVector3D(trsf.b1, trsf.b2, trsf.b3).Length(); - const ai_real scalingZ = aiVector3D(trsf.c1, trsf.c2, trsf.c3).Length(); - return aiVector3D(scalingX, scalingY, scalingZ); -} - -bool hasScaleFactor(const gp_Trsf& trsf) -{ - const double topLocationScalePrec = -#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) - TopLoc_Location::ScalePrec(); -#else - 1.e-14; -#endif - return (Abs(Abs(trsf.ScaleFactor()) - 1.) > topLocationScalePrec) || trsf.IsNegative(); + aiVector3D scaling; + aiQuaternion rotation; + aiVector3D position; + trsf.Decompose(scaling, rotation, position); + return scaling; } bool hasScaleFactor(const aiVector3D& scaling) @@ -78,54 +70,144 @@ bool hasScaleFactor(const aiVector3D& scaling) ); } -[[maybe_unused]] bool hasScaleFactor(const aiMatrix4x4& trsf) +// Visit each node in Assimp tree and call 'fnCallback' +void deep_aiNodeVisit( + const aiNode* node, + const std::function& fnPreCallback, + const std::function& fnPostCallback = nullptr + ) { - const aiVector3D scaling = aiMatrixScaling(trsf); - return hasScaleFactor(scaling); + if (fnPreCallback) + fnPreCallback(node); + + for (unsigned ichild = 0; ichild < node->mNumChildren; ++ichild) + deep_aiNodeVisit(node->mChildren[ichild], fnPreCallback, fnPostCallback); + + if (fnPostCallback) + fnPostCallback(node); } // Check if Assimp tree from 'node' contains a transformation having scale bool deep_aiNodeTransformationHasScaling(const aiNode* node) { - const aiVector3D scaling = aiMatrixScaling(node->mTransformation); - if (!MathUtils::fuzzyEqual(scaling.x, 1) - || !MathUtils::fuzzyEqual(scaling.y, 1) - || !MathUtils::fuzzyEqual(scaling.z, 1) - || scaling.x < 0. || scaling.y < 0. || scaling.z < 0. - ) - { - //std::cout << "[TRACE] hasScaling: " << scaling.x << " " << scaling.y << " " << scaling.z << std::endl; - return true; + bool hasScaling = false; + deep_aiNodeVisit(node, [&](const aiNode* node) { + if (!hasScaling) { + const aiVector3D scaling = aiMatrixScaling(node->mTransformation); + hasScaling = + !MathUtils::fuzzyEqual(scaling.x, 1) + || !MathUtils::fuzzyEqual(scaling.y, 1) + || !MathUtils::fuzzyEqual(scaling.z, 1) + || scaling.x < 0. || scaling.y < 0. || scaling.z < 0. + ; + } + }); + + return hasScaling; +} + +void deep_aiScenePrint(std::ostream& outs, const aiScene* scene) +{ + auto fnIndent = [](std::ostream& outs, int depth) -> std::ostream& { + for (int i = 0; i < depth * 4; ++i) + outs << ' '; + return outs; + }; + + outs << "#animation: " << scene->mNumAnimations << std::endl; + outs << "#camera: " << scene->mNumCameras<< std::endl; + outs << "#light: " << scene->mNumLights<< std::endl; + outs << "#mesh: " << scene->mNumMeshes<< std::endl; + outs << "#material: " << scene->mNumMaterials<< std::endl; + // TODO Skeleton data is available in assimp >= 5.2.5 + // Add detection in CMakeLists.txt + // outs << "#skeleton: " << scene->mNumSkeletons<< std::endl; + outs << "#texture: " << scene->mNumTextures<< std::endl; + outs << std::endl; + + for (unsigned ianim = 0; ianim < scene->mNumAnimations; ++ianim) { + const aiAnimation* anim = scene->mAnimations[ianim]; + outs << "Animation" << ianim << std::endl; + outs << " name: '" << anim->mName.C_Str() << "'\n"; + outs << " duration: " << anim->mDuration << "\n"; + outs << " ticksPerSecond: " << anim->mTicksPerSecond << "\n"; + outs << " #channel: " << anim->mNumChannels << "\n"; + outs << " #meshChannel: " << anim->mNumMeshChannels << "\n"; + outs << " #morphMeshChannel: " << anim->mNumMorphMeshChannels << "\n"; + for (unsigned ichannel = 0; ichannel < anim->mNumChannels; ++ichannel) { + const aiNodeAnim* iNodeAnim = anim->mChannels[ichannel]; + outs << " NodeAnim" << ichannel << "\n"; + outs << " nodeName: '" << iNodeAnim->mNodeName.C_Str() << "'\n"; + outs << " #posKey: " << iNodeAnim->mNumPositionKeys << "\n"; + outs << " #rotKey: " << iNodeAnim->mNumRotationKeys << "\n"; + outs << " #scaleKey: " << iNodeAnim->mNumScalingKeys << "\n"; +#if 0 + outs << " ScalingKeys" << "\n"; + for (unsigned iKey = 0; iKey < iNodeAnim->mNumScalingKeys; ++iKey) { + const aiVector3D& vec = iNodeAnim->mScalingKeys[iKey].mValue; + outs << " " << vec.x << ", " << vec.y << ", " << vec.z << "\n"; + } +#endif + } + + outs << std::endl; } - for (unsigned ichild = 0; ichild < node->mNumChildren; ++ichild) { - if (deep_aiNodeTransformationHasScaling(node->mChildren[ichild])) - return true; + for (unsigned imesh = 0; imesh < scene->mNumMeshes; ++imesh) { + const aiMesh* mesh = scene->mMeshes[imesh]; + outs << "Mesh" << imesh << "\n" + << " name: '" << mesh->mName.C_Str() << "'\n" + << " materialid: " << mesh->mMaterialIndex << "\n" + << " #vert: " << mesh->mNumVertices << "\n" + << " #face: " << mesh->mNumFaces << "\n" + << " #bone: " << mesh->mNumBones << "\n"; + for (unsigned ibone = 0; ibone < mesh->mNumBones; ++ibone) { + const aiBone* bone = mesh->mBones[ibone]; + outs << " Bone" << ibone << "\n" + << " name: '" << bone->mName.C_Str() << "'\n" + << " #weight: " << bone->mNumWeights + << "\n"; + } } - return false; -} + outs << "\nScene graph:\n" ; + int nodeDepth = 0; + deep_aiNodeVisit( + scene->mRootNode, + [&](const aiNode* node) { + fnIndent(outs, nodeDepth); + outs << node->mName.C_Str() << " {"; + if (node->mNumMeshes) + outs << "#mesh:" << node->mNumMeshes << " "; + + if (node->mNumChildren) + outs << "#child:" << node->mNumChildren; + + outs << "}\n"; + if (node->mMetaData) { + fnIndent(outs, nodeDepth) << " "; + outs << "->metada " << " #prop:" << node->mMetaData->mNumProperties; + for (unsigned ikey = 0; ikey < node->mMetaData->mNumProperties; ++ikey) { + outs << " key:'" << node->mMetaData->mKeys[ikey].C_Str() << "'"; + } + + outs << "\n"; + } -// Visit each node in Assimp tree and call 'fnCallback' -void deep_aiNodeVisit(const aiNode* node, const std::function& fnCallback) -{ - fnCallback(node); - for (unsigned ichild = 0; ichild < node->mNumChildren; ++ichild) - deep_aiNodeVisit(node->mChildren[ichild], fnCallback); -} + for (unsigned imesh = 0; imesh < node->mNumMeshes; ++imesh) { + const aiMesh* mesh = scene->mMeshes[node->mMeshes[imesh]]; + fnIndent(outs, nodeDepth) << " "; + outs << "->mesh" << imesh << " '" << mesh->mName.C_Str() << "'" + << " meshid:" << node->mMeshes[imesh] + << "\n"; + } -// Returns the OpenCascade transformation converted from assimp matrix -gp_Trsf toOccTrsf(const aiMatrix4x4& matrix) -{ - // TODO Check scaling != 0 - const aiVector3D scaling = aiMatrixScaling(matrix); - gp_Trsf trsf; - trsf.SetValues( - matrix.a1 / scaling.x, matrix.a2 / scaling.x, matrix.a3 / scaling.x, matrix.a4, - matrix.b1 / scaling.y, matrix.b2 / scaling.y, matrix.b3 / scaling.y, matrix.b4, - matrix.c1 / scaling.z, matrix.c2 / scaling.z, matrix.c3 / scaling.z, matrix.c4 + ++nodeDepth; + }, + [&](const aiNode*) { --nodeDepth; } ); - return trsf; + + outs << std::endl; } // Returns the Quantity_Color object equivalent to assimp 'color' @@ -239,7 +321,6 @@ bool AssimpReader::readFile(const FilePath& filepath, TaskProgress* progress) aiProcess_Triangulate | aiProcess_JoinIdenticalVertices //| aiProcess_SortByPType /* Crashes with assimp-5.3.1 on Windows */ - //| aiProcess_OptimizeGraph //| aiProcess_TransformUVCoords //| aiProcess_FlipUVs @@ -305,6 +386,9 @@ bool AssimpReader::readFile(const FilePath& filepath, TaskProgress* progress) m_vecMaterial.at(i) = this->createOccVisMaterial(material, filepath); } +#ifdef MAYO_ASSIMP_READER_HANDLE_SCALING + deep_aiScenePrint(std::cout, m_scene); +#endif return true; } @@ -314,6 +398,7 @@ TDF_LabelSequence AssimpReader::transfer(DocumentPtr doc, TaskProgress* progress return {}; m_mapNodeData.clear(); + // Compute data for each aiNode object in the scene deep_aiNodeVisit(m_scene->mRootNode, [=](const aiNode* node) { aiNodeData nodeData; @@ -321,14 +406,12 @@ TDF_LabelSequence AssimpReader::transfer(DocumentPtr doc, TaskProgress* progress if (itParentData != m_mapNodeData.cend()) { const aiNodeData& parentData = itParentData->second; nodeData.aiAbsoluteTrsf = parentData.aiAbsoluteTrsf * node->mTransformation; - nodeData.occAbsoluteTrsf = parentData.occAbsoluteTrsf * toOccTrsf(node->mTransformation); } else { nodeData.aiAbsoluteTrsf = node->mTransformation; - nodeData.occAbsoluteTrsf = toOccTrsf(node->mTransformation); } - m_mapNodeData.insert({ node, nodeData }); + m_mapNodeData.insert_or_assign(node, std::move(nodeData)); }); // Compute count of meshes in the scene @@ -371,7 +454,7 @@ void AssimpReader::applyProperties(const PropertyGroup* group) } OccHandle AssimpReader::findOccTexture( - const std::string& strFilepath, const FilePath& modelFilepath + const std::string& strFilepath, const FilePath& modelFilepath ) { // Texture might be embedded @@ -426,7 +509,7 @@ OccHandle AssimpReader::findOccTexture( } OccHandle AssimpReader::createOccVisMaterial( - const aiMaterial* material, const FilePath& modelFilepath + const aiMaterial* material, const FilePath& modelFilepath ) { auto mat = makeOccHandle(); @@ -610,10 +693,10 @@ OccHandle AssimpReader::createOccVisMaterial( } void AssimpReader::transferSceneNode( - const aiNode* node, - DocumentPtr targetDoc, - const TDF_Label& labelEntity, - const std::function& fnCallbackMesh + const aiNode* node, + DocumentPtr targetDoc, + const TDF_Label& labelEntity, + const std::function& fnCallbackMesh ) { if (!node) @@ -622,14 +705,14 @@ void AssimpReader::transferSceneNode( const std::string nodeName = node->mName.C_Str(); const aiNodeData nodeData = Cpp::findValue(node, m_mapNodeData); - gp_Trsf nodeAbsoluteTrsf = nodeData.occAbsoluteTrsf; - if (hasScaleFactor(nodeAbsoluteTrsf)) - nodeAbsoluteTrsf.SetScaleFactor(1.); + aiVector3D nodeScale; + aiQuaternion nodeRot; + aiVector3D nodePos; + nodeData.aiAbsoluteTrsf.Decompose(nodeScale, nodeRot, nodePos); -#ifdef MAYO_ASSIMP_READER_HANDLE_SCALING - const aiVector3D nodeScaling = aiMatrixScaling(nodeData.aiAbsoluteTrsf); - const bool nodeHasScaling = hasScaleFactor(nodeScaling); -#endif + gp_Trsf nodeAbsoluteTrsf; + nodeAbsoluteTrsf.SetRotationPart(gp_Quaternion{nodeRot.x, nodeRot.y, nodeRot.z, nodeRot.w}); + nodeAbsoluteTrsf.SetTranslationPart(gp_Vec{nodePos.x, nodePos.y, nodePos.z}); // Produce shape corresponding to the node for (unsigned imesh = 0; imesh < node->mNumMeshes; ++imesh) { @@ -641,14 +724,13 @@ void AssimpReader::transferSceneNode( continue; // Skip #ifdef MAYO_ASSIMP_READER_HANDLE_SCALING - if (nodeHasScaling) { + if (hasScaleFactor(nodeScale)) { triangulation = triangulation->Copy(); - for (int i = 1; i < triangulation->NbNodes(); ++i) { - gp_Pnt pnt = triangulation->Node(i); - pnt.SetX(pnt.X() * nodeScaling.x); - pnt.SetY(pnt.Y() * nodeScaling.y); - pnt.SetZ(pnt.Z() * nodeScaling.z); - MeshUtils::setNode(triangulation, i , pnt); + for (int i = 1; i <= triangulation->NbNodes(); ++i) { + const gp_Pnt pnt = triangulation->Node(i); + MeshUtils::setNode( + triangulation, i, gp_Pnt{ pnt.X() * nodeScale.x, pnt.Y() * nodeScale.y, pnt.Z() * nodeScale.z } + ); } } #endif diff --git a/src/io_assimp/io_assimp_reader.h b/src/io_assimp/io_assimp_reader.h index 41845f64..1d8f7ab0 100644 --- a/src/io_assimp/io_assimp_reader.h +++ b/src/io_assimp/io_assimp_reader.h @@ -6,10 +6,7 @@ #pragma once -#include "../base/document_ptr.h" #include "../base/io_reader.h" -#include "../base/occ_handle.h" -#include "../base/tkernel_utils.h" #include @@ -57,7 +54,6 @@ class AssimpReader : public Reader { ); struct aiNodeData { - gp_Trsf occAbsoluteTrsf; aiMatrix4x4 aiAbsoluteTrsf; }; From 5f4160ec558c87385d07b8c31f573f66193e1f7a Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 20 May 2024 10:35:56 +0200 Subject: [PATCH 071/101] CI: introduce macOS build for Qt6 --- .github/workflows/ci_linux.yml | 1 + .github/workflows/ci_macos.yml | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index eb06de52..6967d4e3 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -9,6 +9,7 @@ jobs: qt_version: [5.15.2, 6.5.2] runs-on: ubuntu-latest + name: build-linux-qt${{matrix.qt_version}} steps: - name: Checkout diff --git a/.github/workflows/ci_macos.yml b/.github/workflows/ci_macos.yml index b74f3c2d..5baf36e0 100644 --- a/.github/workflows/ci_macos.yml +++ b/.github/workflows/ci_macos.yml @@ -4,14 +4,23 @@ on: [push] jobs: build-macos: + strategy: + matrix: + include: + - qt_version_name: 5.15 + qt_version_major: 5 + - qt_version_name: 6.7 + qt_version_major: 6 + runs-on: macos-latest + name: build-macos-qt${{matrix.qt_version_name}} steps: - name: Checkout uses: actions/checkout@v2 - name: Install Qt - run: brew install qt@5 + run: brew install qt@${{matrix.qt_version_major}} - name: Install OpenCascade run: brew install opencascade @@ -31,7 +40,7 @@ jobs: cmake .. \ -DMayo_BuildTests=ON \ -DMayo_BuildPluginAssimp=ON \ - -DQT_DIR=$(brew --prefix qt5)/lib/cmake/Qt5 + -DQT_DIR=$(brew --prefix qt@${{matrix.qt_version_major}})/lib/cmake/Qt${{matrix.qt_version_major}} cmake --build . \ --config Release \ --parallel ${{steps.cpu-cores.outputs.count}} From 223fe7bf140fa36452ac5f43fc85e5dcd7c27483 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 10 Jun 2024 10:43:07 +0200 Subject: [PATCH 072/101] App: minor fixes about code indentation --- src/app/dialog_inspect_xde.cpp | 53 ++++++++++++++++++++++------------ src/app/item_view_buttons.h | 3 +- src/app/list_helper.h | 6 ++-- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/app/dialog_inspect_xde.cpp b/src/app/dialog_inspect_xde.cpp index 56d4ef4f..26398162 100644 --- a/src/app/dialog_inspect_xde.cpp +++ b/src/app/dialog_inspect_xde.cpp @@ -9,6 +9,7 @@ #include "../base/application.h" #include "../base/brep_utils.h" #include "../base/caf_utils.h" +#include "../base/cpp_utils.h" #include "../base/occ_handle.h" #include "../base/meta_enum.h" #include "../base/settings.h" @@ -258,7 +259,8 @@ static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const TopoDS QString("%1%2%3") .arg(vertexCount ? QString("%1 vertices ").arg(vertexCount) : QString()) .arg(edgeCount ? QString("%1 edges ").arg(edgeCount) : QString()) - .arg(faceCount ? QString("%1 faces").arg(faceCount) : QString()); + .arg(faceCount ? QString("%1 faces").arg(faceCount) : QString()) + ; itemShape->setText(1, textShape); return itemShape; @@ -441,7 +443,8 @@ static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const OccHan static void loadLabelVisMaterialProperties( const TDF_Label& label, const OccHandle& visMaterialTool, - QTreeWidgetItem* treeItem) + QTreeWidgetItem* treeItem + ) { QList listItemProp; auto fnAddItem = [&](QTreeWidgetItem* item) { listItemProp.push_back(item); }; @@ -518,7 +521,8 @@ static void loadLabelVisMaterialProperties( static void loadLabelColorProperties( const TDF_Label& label, const OccHandle& colorTool, - QTreeWidgetItem* treeItem) + QTreeWidgetItem* treeItem + ) { QList listItemProp; auto fnAddItem = [&](QTreeWidgetItem* item) { listItemProp.push_back(item); }; @@ -547,7 +551,8 @@ static void loadLabelColorProperties( static void loadLabelShapeProperties( const TDF_Label& label, const OccHandle& shapeTool, - QTreeWidgetItem* treeItem) + QTreeWidgetItem* treeItem + ) { QList listItemProp; auto fnAddItem = [&](QTreeWidgetItem* item) { listItemProp.push_back(item); }; @@ -572,7 +577,8 @@ static void loadLabelShapeProperties( const QString textItemRefShape = to_QString(CafUtils::labelTag(labelRef)) + " " - + to_QString(CafUtils::labelAttrStdName(labelRef)); + + to_QString(CafUtils::labelAttrStdName(labelRef)) + ; fnAddItem(createPropertyTreeItem("ReferredShape", textItemRefShape)); } } @@ -592,9 +598,11 @@ static void loadLabelDimensionProperties(const TDF_Label& label, QTreeWidgetItem OccHandle dimObject = dimAttr->GetObject(); fnAddItem(createPropertyTreeItem("SemanticName", dimObject->GetSemanticName())); fnAddItem(createPropertyTreeItem( - "Qualifier", MetaEnum::nameWithoutPrefix(dimObject->GetQualifier(), "XCAFDimTolObjects_"))); + "Qualifier", MetaEnum::nameWithoutPrefix(dimObject->GetQualifier(), "XCAFDimTolObjects_")) + ); fnAddItem(createPropertyTreeItem( - "Type", MetaEnum::nameWithoutPrefix(dimObject->GetType(), "XCAFDimTolObjects_"))); + "Type", MetaEnum::nameWithoutPrefix(dimObject->GetType(), "XCAFDimTolObjects_")) + ); fnAddItem(createPropertyTreeItem("Value", dimObject->GetValue())); fnAddItem(createPropertyTreeItem("IsDimWithRange", dimObject->IsDimWithRange())); @@ -677,7 +685,8 @@ static void loadLabelDatumProperties(const TDF_Label& label, QTreeWidgetItem* tr fnAddItem(createPropertyTreeItem("IsDatumTarget", datumObject->IsDatumTarget())); fnAddItem(createPropertyTreeItem( "DatumTargetType", - MetaEnum::nameWithoutPrefix(datumObject->GetDatumTargetType(), "XCAFDimTolObjects_"))); + MetaEnum::nameWithoutPrefix(datumObject->GetDatumTargetType(), "XCAFDimTolObjects_")) + ); fnAddItem(createPropertyTreeItem("HasDatumTargetParams", datumObject->HasDatumTargetParams())); if (datumObject->HasDatumTargetParams()) { fnAddChildItem(createPropertyTreeItem("DatumTargetAxis", datumObject->GetDatumTargetAxis())); @@ -716,16 +725,20 @@ static void loadLabelGeomToleranceProperties(const TDF_Label& label, QTreeWidget OccHandle tolObject = tolAttr->GetObject(); fnAddItem(createPropertyTreeItem("SemanticName", tolObject->GetSemanticName())); fnAddItem(createPropertyTreeItem( - "Type", MetaEnum::nameWithoutPrefix(tolObject->GetType(), "XCAFDimTolObjects_"))); + "Type", MetaEnum::nameWithoutPrefix(tolObject->GetType(), "XCAFDimTolObjects_")) + ); fnAddItem(createPropertyTreeItem( - "TypeOfValue", MetaEnum::nameWithoutPrefix(tolObject->GetTypeOfValue(), "XCAFDimTolObjects_"))); + "TypeOfValue", MetaEnum::nameWithoutPrefix(tolObject->GetTypeOfValue(), "XCAFDimTolObjects_")) + ); fnAddItem(createPropertyTreeItem("Value", tolObject->GetValue())); fnAddItem(createPropertyTreeItem( "MaterialRequirementModifier", - MetaEnum::nameWithoutPrefix(tolObject->GetMaterialRequirementModifier(), "XCAFDimTolObjects_"))); + MetaEnum::nameWithoutPrefix(tolObject->GetMaterialRequirementModifier(), "XCAFDimTolObjects_")) + ); fnAddItem(createPropertyTreeItem( "ZoneModifier", - MetaEnum::nameWithoutPrefix(tolObject->GetZoneModifier(), "XCAFDimTolObjects_"))); + MetaEnum::nameWithoutPrefix(tolObject->GetZoneModifier(), "XCAFDimTolObjects_")) + ); fnAddItem(createPropertyTreeItem("ValueOfZoneModifier", tolObject->GetValueOfZoneModifier())); fnAddItem(createPropertyTreeItem("MaxValueModifier", tolObject->GetMaxValueModifier())); fnAddItem(createPropertyTreeItem("HasAxis", tolObject->HasAxis())); @@ -750,9 +763,11 @@ static void loadLabelGeomToleranceProperties(const TDF_Label& label, QTreeWidget if (tolObject->HasAffectedPlane()) { fnAddChildItem(createPropertyTreeItem( "AffectedPlaneType", - MetaEnum::nameWithoutPrefix(tolObject->GetAffectedPlaneType(), "XCAFDimTolObjects_"))); + MetaEnum::nameWithoutPrefix(tolObject->GetAffectedPlaneType(), "XCAFDimTolObjects_")) + ); fnAddChildItem(createPropertyTreeItem( - "AffectedPlaneType", tolObject->GetAffectedPlane().Position().Ax2())); + "AffectedPlaneType", tolObject->GetAffectedPlane().Position().Ax2()) + ); } treeItem->addChildren(listItemProp); @@ -770,7 +785,7 @@ static void loadLabel(const TDF_Label& label, QTreeWidgetItem* treeItem) static void deepLoadChildrenLabels(const TDF_Label& label, QTreeWidgetItem* treeItem) { - for (TDF_ChildIterator it(label, Standard_False); it.More(); it.Next()) { + for (TDF_ChildIterator it(label, false/*!allLevels*/); it.More(); it.Next()) { const TDF_Label childLabel = it.Value(); auto childTreeItem = new QTreeWidgetItem(treeItem); loadLabel(childLabel, childTreeItem); @@ -788,8 +803,9 @@ DialogInspectXde::DialogInspectXde(QWidget* parent) m_ui->splitter->setStretchFactor(0, 1); m_ui->splitter->setStretchFactor(1, 4); QObject::connect( - m_ui->treeWidget_Document, &QTreeWidget::itemClicked, - this, &DialogInspectXde::onLabelTreeWidgetItemClicked); + m_ui->treeWidget_Document, &QTreeWidget::itemClicked, + this, &DialogInspectXde::onLabelTreeWidgetItemClicked + ); } DialogInspectXde::~DialogInspectXde() @@ -801,8 +817,7 @@ void DialogInspectXde::load(const OccHandle& doc) { m_doc = doc; if (!XCAFDoc_DocumentTool::IsXCAFDocument(doc)) { - QtWidgetsUtils::asyncMsgBoxCritical( - this, tr("Error"), tr("This document is not suitable for XDE")); + QtWidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), tr("This document is not suitable for XDE")); return; } diff --git a/src/app/item_view_buttons.h b/src/app/item_view_buttons.h index c698e754..62480163 100644 --- a/src/app/item_view_buttons.h +++ b/src/app/item_view_buttons.h @@ -106,7 +106,8 @@ class ItemViewButtons : public QObject { void installDefaultItemDelegate(); QStyledItemDelegate* createProxyItemDelegate( QStyledItemDelegate* sourceDelegate, - QObject* parent = nullptr) const; + QObject* parent = nullptr + ) const; signals: void buttonClicked(int btnId, const QModelIndex& index); diff --git a/src/app/list_helper.h b/src/app/list_helper.h index 81c02013..2c272939 100644 --- a/src/app/list_helper.h +++ b/src/app/list_helper.h @@ -71,7 +71,8 @@ class ItemDelegate : public QStyledItemDelegate { void paint( QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + const QModelIndex& index + ) const override; QSize itemSize() const { return m_itemSize; } void setItemSize(QSize size); @@ -87,7 +88,8 @@ class ItemDelegate : public QStyledItemDelegate { QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, - const QModelIndex& index) override; + const QModelIndex& index + ) override; virtual void clickAction(const ModelItem*) const { } From 64925c4ce0f0de121b0ccfa6c1ab08b788e9f81f Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 10 Jun 2024 10:46:49 +0200 Subject: [PATCH 073/101] Fix remaining compiler warnings on Windows/MSVC --- src/app/dialog_inspect_xde.cpp | 5 +++-- src/base/string_conv.cpp | 2 +- src/io_ply/miniply.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app/dialog_inspect_xde.cpp b/src/app/dialog_inspect_xde.cpp index 26398162..2d6ad707 100644 --- a/src/app/dialog_inspect_xde.cpp +++ b/src/app/dialog_inspect_xde.cpp @@ -431,8 +431,9 @@ static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const OccHan else if (imgTexture->DataBuffer() && !imgTexture->DataBuffer()->IsEmpty()) { // Texture is provided by some embedded data item->setText(1, DialogInspectXde::tr("")); - const OccHandle& buff = imgTexture->DataBuffer(); - item->setImage(1, QByteArray::fromRawData(reinterpret_cast(buff->Data()), buff->Size())); + const char* buffData = reinterpret_cast(imgTexture->DataBuffer()->Data()); + const int buffSize = Cpp::safeStaticCast(imgTexture->DataBuffer()->Size()); + item->setImage(1, QByteArray::fromRawData(buffData, buffSize)); } return item; diff --git a/src/base/string_conv.cpp b/src/base/string_conv.cpp index 8c8dbf07..0002ff3b 100644 --- a/src/base/string_conv.cpp +++ b/src/base/string_conv.cpp @@ -89,7 +89,7 @@ std::string toUtf8String(std::string_view str, const std::locale& locale) return {}; // Encode intermediate utf16 string to utf8 - const int lenUtf16 = utf16.size() - 1; + const int lenUtf16 = Cpp::safeStaticCast(utf16.size()) - 1; const int lenUtf8 = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), lenUtf16, nullptr, 0, nullptr, nullptr); thread_local std::vector utf8; utf8.resize(lenUtf8 + 1); diff --git a/src/io_ply/miniply.cpp b/src/io_ply/miniply.cpp index bd0097d7..9977bf7a 100644 --- a/src/io_ply/miniply.cpp +++ b/src/io_ply/miniply.cpp @@ -1126,7 +1126,7 @@ namespace miniply { return 0; } const PLYProperty& prop = element()->properties[propIdx]; - return prop.listData.size() / kPLYPropertySize[uint32_t(prop.type)]; + return static_cast(prop.listData.size() / kPLYPropertySize[uint32_t(prop.type)]); } From 0487b5f904c13432dffb1a6e8db0955712fd83c8 Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 10 Jun 2024 11:04:36 +0200 Subject: [PATCH 074/101] CI: change to Debug(instead of Release) config on Windows/MSVC --- .github/workflows/ci_windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci_windows.yml b/.github/workflows/ci_windows.yml index 83fac0a8..9b5f524d 100644 --- a/.github/workflows/ci_windows.yml +++ b/.github/workflows/ci_windows.yml @@ -87,12 +87,12 @@ jobs: -DOpenCASCADE_DIR=../OpenCASCADE-${{matrix.occ_version}}-vc14-64/opencascade-${{matrix.occ_version}} ^ -Dassimp_DIR=${{github.workspace}}/assimp-5.3.1/lib/cmake/assimp-5.3 cmake --build . ^ - --config Release ^ + --config Debug ^ --parallel ${{steps.cpu-cores.outputs.count}} - name: Execute Unit Tests working-directory: ${{github.workspace}}/build shell: cmd run: | - release\mayo.exe --runtests -o utests-output.txt + debug\mayo.exe --runtests -o utests-output.txt more utests-output.txt From ca58e3c861006116d98fff373a7716b3267f325b Mon Sep 17 00:00:00 2001 From: Hugues Delorme Date: Mon, 10 Jun 2024 18:12:16 +0200 Subject: [PATCH 075/101] 3rdparty: update fmtlib to latest --- CMakeLists.txt | 2 + src/3rdparty/commit_fmt.txt | 2 +- src/3rdparty/fmt/args.h | 146 +- src/3rdparty/fmt/base.h | 3051 ++++++++++++++++++++++++ src/3rdparty/fmt/chrono.h | 2041 ++++++++++++---- src/3rdparty/fmt/color.h | 388 ++- src/3rdparty/fmt/compile.h | 334 +-- src/3rdparty/fmt/core.h | 3165 +------------------------ src/3rdparty/fmt/format-inl.h | 2914 +++++++++-------------- src/3rdparty/fmt/format.h | 4056 +++++++++++++++++++++----------- src/3rdparty/fmt/locale.h | 2 - src/3rdparty/fmt/os.h | 426 ++-- src/3rdparty/fmt/ostream.h | 242 +- src/3rdparty/fmt/printf.h | 521 ++-- src/3rdparty/fmt/ranges.h | 1043 ++++---- src/3rdparty/fmt/src/fmt.cc | 140 +- src/3rdparty/fmt/src/format.cc | 111 +- src/3rdparty/fmt/src/os.cc | 247 +- src/3rdparty/fmt/std.h | 651 +++++ src/3rdparty/fmt/xchar.h | 287 ++- 20 files changed, 10978 insertions(+), 8791 deletions(-) create mode 100644 src/3rdparty/fmt/base.h delete mode 100644 src/3rdparty/fmt/locale.h create mode 100644 src/3rdparty/fmt/std.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c5449b89..fdb96b3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,6 +143,8 @@ if(MSVC) list(APPEND Mayo_CompileDefinitions NOMINMAX) # Deletion of pointer to incomplete type 'XXXX'; no destructor called list(APPEND Mayo_CompileOptions /we4150) + # Specify both the source character set and the execution character set as UTF-8 + list(APPEND Mayo_CompileOptions /utf-8) if(QT_VERSION_MAJOR EQUAL 6) list(APPEND Mayo_CompileDefinitions _USE_MATH_DEFINES) endif() diff --git a/src/3rdparty/commit_fmt.txt b/src/3rdparty/commit_fmt.txt index 11bb43db..f7513e0b 100644 --- a/src/3rdparty/commit_fmt.txt +++ b/src/3rdparty/commit_fmt.txt @@ -1 +1 @@ -2742611cad4aee6b1a5638bd1ebf132908f4a3d9 \ No newline at end of file +18a325f370ffd5ec1eda0087d2efc3dc9b3faf56 diff --git a/src/3rdparty/fmt/args.h b/src/3rdparty/fmt/args.h index 36f62220..10c1eb8f 100644 --- a/src/3rdparty/fmt/args.h +++ b/src/3rdparty/fmt/args.h @@ -1,4 +1,4 @@ -// Formatting library for C++ - dynamic format arguments +// Formatting library for C++ - dynamic argument lists // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. @@ -8,11 +8,13 @@ #ifndef FMT_ARGS_H_ #define FMT_ARGS_H_ -#include // std::reference_wrapper -#include // std::unique_ptr -#include +#ifndef FMT_IMPORT_STD +# include // std::reference_wrapper +# include // std::unique_ptr +# include +#endif -#include "core.h" +#include "format.h" // std_string_view FMT_BEGIN_NAMESPACE @@ -22,20 +24,24 @@ template struct is_reference_wrapper : std::false_type {}; template struct is_reference_wrapper> : std::true_type {}; -template const T& unwrap(const T& v) { return v; } -template const T& unwrap(const std::reference_wrapper& v) { +template auto unwrap(const T& v) -> const T& { return v; } +template +auto unwrap(const std::reference_wrapper& v) -> const T& { return static_cast(v); } -class dynamic_arg_list { - // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for - // templates it doesn't complain about inability to deduce single translation - // unit for placing vtable. So storage_node_base is made a fake template. - template struct node { - virtual ~node() = default; - std::unique_ptr> next; - }; +// node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC +// 2022 (v17.10.0). +// +// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for +// templates it doesn't complain about inability to deduce single translation +// unit for placing vtable. So node is made a fake template. +template struct node { + virtual ~node() = default; + std::unique_ptr> next; +}; +class dynamic_arg_list { template struct typed_node : node<> { T value; @@ -50,7 +56,7 @@ class dynamic_arg_list { std::unique_ptr> head_; public: - template const T& push(const Arg& arg) { + template auto push(const Arg& arg) -> const T& { auto new_node = std::unique_ptr>(new typed_node(arg)); auto& value = new_node->value; new_node->next = std::move(head_); @@ -61,14 +67,10 @@ class dynamic_arg_list { } // namespace detail /** - \rst - A dynamic version of `fmt::format_arg_store`. - It's equipped with a storage to potentially temporary objects which lifetimes - could be shorter than the format arguments object. - - It can be implicitly converted into `~fmt::basic_format_args` for passing - into type-erased formatting functions such as `~fmt::vformat`. - \endrst + * A dynamic list of formatting arguments with storage. + * + * It can be implicitly converted into `fmt::basic_format_args` for passing + * into type-erased formatting functions such as `fmt::vformat`. */ template class dynamic_format_arg_store @@ -95,10 +97,10 @@ class dynamic_format_arg_store }; template - using stored_type = conditional_t::value && - !has_formatter::value && - !detail::is_reference_wrapper::value, - std::basic_string, T>; + using stored_type = conditional_t< + std::is_convertible>::value && + !detail::is_reference_wrapper::value, + std::basic_string, T>; // Storage of basic_format_arg must be contiguous. std::vector> data_; @@ -110,14 +112,14 @@ class dynamic_format_arg_store friend class basic_format_args; - unsigned long long get_types() const { + auto get_types() const -> unsigned long long { return detail::is_unpacked_bit | data_.size() | (named_info_.empty() ? 0ULL : static_cast(detail::has_named_args_bit)); } - const basic_format_arg* data() const { + auto data() const -> const basic_format_arg* { return named_info_.empty() ? data_.data() : data_.data() + 1; } @@ -145,33 +147,21 @@ class dynamic_format_arg_store public: constexpr dynamic_format_arg_store() = default; - constexpr dynamic_format_arg_store( - const dynamic_format_arg_store& store) - : -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - basic_format_args(), -#endif - data_(store.data_), - named_info_(store.named_info_) { - } - /** - \rst - Adds an argument into the dynamic store for later passing to a formatting - function. - - Note that custom types and string types (but not string views) are copied - into the store dynamically allocating memory if necessary. - - **Example**:: - - fmt::dynamic_format_arg_store store; - store.push_back(42); - store.push_back("abc"); - store.push_back(1.5f); - std::string result = fmt::vformat("{} and {} and {}", store); - \endrst - */ + * Adds an argument into the dynamic store for later passing to a formatting + * function. + * + * Note that custom types and string types (but not string views) are copied + * into the store dynamically allocating memory if necessary. + * + * **Example**: + * + * fmt::dynamic_format_arg_store store; + * store.push_back(42); + * store.push_back("abc"); + * store.push_back(1.5f); + * std::string result = fmt::vformat("{} and {} and {}", store); + */ template void push_back(const T& arg) { if (detail::const_check(need_copy::value)) emplace_arg(dynamic_args_.push>(arg)); @@ -180,20 +170,18 @@ class dynamic_format_arg_store } /** - \rst - Adds a reference to the argument into the dynamic store for later passing to - a formatting function. - - **Example**:: - - fmt::dynamic_format_arg_store store; - char band[] = "Rolling Stones"; - store.push_back(std::cref(band)); - band[9] = 'c'; // Changing str affects the output. - std::string result = fmt::vformat("{}", store); - // result == "Rolling Scones" - \endrst - */ + * Adds a reference to the argument into the dynamic store for later passing + * to a formatting function. + * + * **Example**: + * + * fmt::dynamic_format_arg_store store; + * char band[] = "Rolling Stones"; + * store.push_back(std::cref(band)); + * band[9] = 'c'; // Changing str affects the output. + * std::string result = fmt::vformat("{}", store); + * // result == "Rolling Scones" + */ template void push_back(std::reference_wrapper arg) { static_assert( need_copy::value, @@ -202,10 +190,10 @@ class dynamic_format_arg_store } /** - Adds named argument into the dynamic store for later passing to a formatting - function. ``std::reference_wrapper`` is supported to avoid copying of the - argument. The name is always copied into the store. - */ + * Adds named argument into the dynamic store for later passing to a + * formatting function. `std::reference_wrapper` is supported to avoid + * copying of the argument. The name is always copied into the store. + */ template void push_back(const detail::named_arg& arg) { const char_type* arg_name = @@ -218,19 +206,15 @@ class dynamic_format_arg_store } } - /** Erase all elements from the store */ + /// Erase all elements from the store. void clear() { data_.clear(); named_info_.clear(); dynamic_args_ = detail::dynamic_arg_list(); } - /** - \rst - Reserves space to store at least *new_cap* arguments including - *new_cap_named* named arguments. - \endrst - */ + /// Reserves space to store at least `new_cap` arguments including + /// `new_cap_named` named arguments. void reserve(size_t new_cap, size_t new_cap_named) { FMT_ASSERT(new_cap >= new_cap_named, "Set of arguments includes set of named arguments"); diff --git a/src/3rdparty/fmt/base.h b/src/3rdparty/fmt/base.h new file mode 100644 index 00000000..a75f0bee --- /dev/null +++ b/src/3rdparty/fmt/base.h @@ -0,0 +1,3051 @@ +// Formatting library for C++ - the base API for char/UTF-8 +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_BASE_H_ +#define FMT_BASE_H_ + +// c headers are preferable for performance reasons +#ifndef FMT_MODULE +# include // CHAR_BIT +# include // FILE +# include // strlen +#endif + +#ifndef FMT_IMPORT_STD +// is also included transitively from . +# include // std::byte +# include // std::enable_if +#else +import std; +#endif + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 100202 + +// Detect compiler versions. +#if defined(__clang__) && !defined(__ibmxl__) +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +# define FMT_CLANG_VERSION 0 +#endif +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +# define FMT_GCC_VERSION 0 +#endif +#if defined(__ICL) +# define FMT_ICC_VERSION __ICL +#elif defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#else +# define FMT_ICC_VERSION 0 +#endif +#if defined(_MSC_VER) +# define FMT_MSC_VERSION _MSC_VER +#else +# define FMT_MSC_VERSION 0 +#endif + +// Detect standard library versions. +#ifdef _GLIBCXX_RELEASE +# define FMT_GLIBCXX_RELEASE _GLIBCXX_RELEASE +#else +# define FMT_GLIBCXX_RELEASE 0 +#endif +#ifdef _LIBCPP_VERSION +# define FMT_LIBCPP_VERSION _LIBCPP_VERSION +#else +# define FMT_LIBCPP_VERSION 0 +#endif + +#ifdef _MSVC_LANG +# define FMT_CPLUSPLUS _MSVC_LANG +#else +# define FMT_CPLUSPLUS __cplusplus +#endif + +// Detect __has_*. +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif +#ifdef __has_include +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +// Detect C++14 relaxed constexpr. +#ifdef FMT_USE_CONSTEXPR +// Use the provided definition. +#elif FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L +// GCC only allows throw in constexpr since version 6: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67371. +# define FMT_USE_CONSTEXPR 1 +#elif FMT_ICC_VERSION +# define FMT_USE_CONSTEXPR 0 // https://github.com/fmtlib/fmt/issues/1628 +#elif FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 +# define FMT_USE_CONSTEXPR 1 +#else +# define FMT_USE_CONSTEXPR 0 +#endif +#if FMT_USE_CONSTEXPR +# define FMT_CONSTEXPR constexpr +#else +# define FMT_CONSTEXPR +#endif + +// Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated. +#if !defined(__cpp_lib_is_constant_evaluated) +# define FMT_USE_CONSTEVAL 0 +#elif FMT_CPLUSPLUS < 201709L +# define FMT_USE_CONSTEVAL 0 +#elif FMT_GLIBCXX_RELEASE && FMT_GLIBCXX_RELEASE < 10 +# define FMT_USE_CONSTEVAL 0 +#elif FMT_LIBCPP_VERSION && FMT_LIBCPP_VERSION < 10000 +# define FMT_USE_CONSTEVAL 0 +#elif defined(__apple_build_version__) && __apple_build_version__ < 14000029L +# define FMT_USE_CONSTEVAL 0 // consteval is broken in Apple clang < 14. +#elif FMT_MSC_VERSION && FMT_MSC_VERSION < 1929 +# define FMT_USE_CONSTEVAL 0 // consteval is broken in MSVC VS2019 < 16.10. +#elif defined(__cpp_consteval) +# define FMT_USE_CONSTEVAL 1 +#elif FMT_GCC_VERSION >= 1002 || FMT_CLANG_VERSION >= 1101 +# define FMT_USE_CONSTEVAL 1 +#else +# define FMT_USE_CONSTEVAL 0 +#endif +#if FMT_USE_CONSTEVAL +# define FMT_CONSTEVAL consteval +# define FMT_CONSTEXPR20 constexpr +#else +# define FMT_CONSTEVAL +# define FMT_CONSTEXPR20 +#endif + +#if defined(FMT_USE_NONTYPE_TEMPLATE_ARGS) +// Use the provided definition. +#elif defined(__NVCOMPILER) +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#elif FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif defined(__cpp_nontype_template_args) && \ + __cpp_nontype_template_args >= 201911L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif FMT_CLANG_VERSION >= 1200 && FMT_CPLUSPLUS >= 202002L +// clang 12 already has enough support for {fmt} to use. +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#else +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#endif + +#ifdef FMT_USE_CONCEPTS +// Use the provided definition. +#elif defined(__cpp_concepts) +# define FMT_USE_CONCEPTS 1 +#else +# define FMT_USE_CONCEPTS 0 +#endif + +// Check if exceptions are disabled. +#ifdef FMT_EXCEPTIONS +// Use the provided definition. +#elif defined(__GNUC__) && !defined(__EXCEPTIONS) +# define FMT_EXCEPTIONS 0 +#elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS +# define FMT_EXCEPTIONS 0 +#else +# define FMT_EXCEPTIONS 1 +#endif +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) +# define FMT_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +#elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +#else +# define FMT_FALLTHROUGH +#endif + +// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. +#if FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && !defined(__NVCC__) +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + +#ifndef FMT_NODISCARD +# if FMT_HAS_CPP17_ATTRIBUTE(nodiscard) +# define FMT_NODISCARD [[nodiscard]] +# else +# define FMT_NODISCARD +# endif +#endif + +#ifdef FMT_DEPRECATED +// Use the provided definition. +#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated) +# define FMT_DEPRECATED [[deprecated]] +#else +# define FMT_DEPRECATED /* deprecated */ +#endif + +#ifdef FMT_INLINE +// Use the provided definition. +#elif FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) +#else +# define FMT_ALWAYS_INLINE inline +#endif +// A version of FMT_INLINE to prevent code bloat in debug mode. +#ifdef NDEBUG +# define FMT_INLINE FMT_ALWAYS_INLINE +#else +# define FMT_INLINE inline +#endif + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_VISIBILITY(value) __attribute__((visibility(value))) +#else +# define FMT_VISIBILITY(value) +#endif + +#ifndef FMT_GCC_PRAGMA +// Workaround a _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884 +// and an nvhpc warning: https://github.com/fmtlib/fmt/pull/2582. +# if FMT_GCC_VERSION >= 504 && !defined(__NVCOMPILER) +# define FMT_GCC_PRAGMA(arg) _Pragma(arg) +# else +# define FMT_GCC_PRAGMA(arg) +# endif +#endif + +// GCC < 5 requires this-> in decltype. +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +# define FMT_DECLTYPE_THIS this-> +#else +# define FMT_DECLTYPE_THIS +#endif + +#if FMT_MSC_VERSION +# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) +# define FMT_UNCHECKED_ITERATOR(It) \ + using _Unchecked_type = It // Mark iterator as checked. +#else +# define FMT_MSC_WARNING(...) +# define FMT_UNCHECKED_ITERATOR(It) using unchecked_type = It +#endif + +#ifndef FMT_BEGIN_NAMESPACE +# define FMT_BEGIN_NAMESPACE \ + namespace fmt { \ + inline namespace v10 { +# define FMT_END_NAMESPACE \ + } \ + } +#endif + +#ifndef FMT_EXPORT +# define FMT_EXPORT +# define FMT_BEGIN_EXPORT +# define FMT_END_EXPORT +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +# if defined(FMT_LIB_EXPORT) +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_API FMT_VISIBILITY("default") +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#ifndef FMT_UNICODE +# define FMT_UNICODE 1 +#endif + +// Check if rtti is available. +#ifndef FMT_USE_RTTI +// __RTTI is for EDG compilers. _CPPRTTI is for MSVC. +# if defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || defined(_CPPRTTI) || \ + defined(__INTEL_RTTI__) || defined(__RTTI) +# define FMT_USE_RTTI 1 +# else +# define FMT_USE_RTTI 0 +# endif +#endif + +#define FMT_FWD(...) static_cast(__VA_ARGS__) + +// Enable minimal optimizations for more compact code in debug mode. +FMT_GCC_PRAGMA("GCC push_options") +#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) +FMT_GCC_PRAGMA("GCC optimize(\"Og\")") +#endif + +FMT_BEGIN_NAMESPACE + +// Implementations of enable_if_t and other metafunctions for older systems. +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; +template using bool_constant = std::integral_constant; +template +using remove_reference_t = typename std::remove_reference::type; +template +using remove_const_t = typename std::remove_const::type; +template +using remove_cvref_t = typename std::remove_cv>::type; +template struct type_identity { + using type = T; +}; +template using type_identity_t = typename type_identity::type; +template +using make_unsigned_t = typename std::make_unsigned::type; +template +using underlying_t = typename std::underlying_type::type; + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +// A workaround for gcc 4.8 to make void_t work in a SFINAE context. +template struct void_t_impl { + using type = void; +}; +template using void_t = typename void_t_impl::type; +#else +template using void_t = void; +#endif + +struct monostate { + constexpr monostate() {} +}; + +// An enable_if helper to be used in template parameters which results in much +// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed +// to workaround a bug in MSVC 2019 (see #1140 and #1186). +#ifdef FMT_DOC +# define FMT_ENABLE_IF(...) +#else +# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 +#endif + +// This is defined in base.h instead of format.h to avoid injecting in std. +// It is a template to avoid undesirable implicit conversions to std::byte. +#ifdef __cpp_lib_byte +template ::value)> +inline auto format_as(T b) -> unsigned char { + return static_cast(b); +} +#endif + +namespace detail { +// Suppresses "unused variable" warnings with the method described in +// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. +// (void)var does not work on many Intel compilers. +template FMT_CONSTEXPR void ignore_unused(const T&...) {} + +constexpr auto is_constant_evaluated(bool default_value = false) noexcept + -> bool { +// Workaround for incompatibility between libstdc++ consteval-based +// std::is_constant_evaluated() implementation and clang-14: +// https://github.com/fmtlib/fmt/issues/3247. +#if FMT_CPLUSPLUS >= 202002L && FMT_GLIBCXX_RELEASE >= 12 && \ + (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) + ignore_unused(default_value); + return __builtin_is_constant_evaluated(); +#elif defined(__cpp_lib_is_constant_evaluated) + ignore_unused(default_value); + return std::is_constant_evaluated(); +#else + return default_value; +#endif +} + +// Suppresses "conditional expression is constant" warnings. +template constexpr auto const_check(T value) -> T { return value; } + +FMT_NORETURN FMT_API void assert_fail(const char* file, int line, + const char* message); + +#if defined(FMT_ASSERT) +// Use the provided definition. +#elif defined(NDEBUG) +// FMT_ASSERT is not empty to avoid -Wempty-body. +# define FMT_ASSERT(condition, message) \ + fmt::detail::ignore_unused((condition), (message)) +#else +# define FMT_ASSERT(condition, message) \ + ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ + ? (void)0 \ + : fmt::detail::assert_fail(__FILE__, __LINE__, (message))) +#endif + +#ifdef FMT_USE_INT128 +// Do nothing. +#elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ + !(FMT_CLANG_VERSION && FMT_MSC_VERSION) +# define FMT_USE_INT128 1 +using int128_opt = __int128_t; // An optional native 128-bit integer. +using uint128_opt = __uint128_t; +template inline auto convert_for_visit(T value) -> T { + return value; +} +#else +# define FMT_USE_INT128 0 +#endif +#if !FMT_USE_INT128 +enum class int128_opt {}; +enum class uint128_opt {}; +// Reduce template instantiations. +template auto convert_for_visit(T) -> monostate { return {}; } +#endif + +// Casts a nonnegative integer to unsigned. +template +FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { + FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); + return static_cast>(value); +} + +// A heuristic to detect std::string and std::[experimental::]string_view. +// It is mainly used to avoid dependency on <[experimental/]string_view>. +template +struct is_std_string_like : std::false_type {}; +template +struct is_std_string_like().find_first_of( + typename T::value_type(), 0))>> + : std::true_type {}; + +// Returns true iff the literal encoding is UTF-8. +constexpr auto is_utf8_enabled() -> bool { + // Avoid an MSVC sign extension bug: https://github.com/fmtlib/fmt/pull/2297. + using uchar = unsigned char; + return sizeof("\u00A7") == 3 && uchar("\u00A7"[0]) == 0xC2 && + uchar("\u00A7"[1]) == 0xA7; +} +constexpr auto use_utf8() -> bool { + return !FMT_MSC_VERSION || is_utf8_enabled(); +} + +static_assert(!FMT_UNICODE || use_utf8(), + "Unicode support requires compiling with /utf-8"); + +template FMT_CONSTEXPR auto length(const Char* s) -> size_t { + size_t len = 0; + while (*s++) ++len; + return len; +} + +template +FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n) + -> int { + for (; n != 0; ++s1, ++s2, --n) { + if (*s1 < *s2) return -1; + if (*s1 > *s2) return 1; + } + return 0; +} + +template +struct is_back_insert_iterator : std::false_type {}; +template +struct is_back_insert_iterator< + It, + bool_constant())), + It>::value>> : std::true_type {}; + +// Extracts a reference to the container from *insert_iterator. +template +inline auto get_container(OutputIt it) -> typename OutputIt::container_type& { + struct accessor : OutputIt { + accessor(OutputIt base) : OutputIt(base) {} + using OutputIt::container; + }; + return *accessor(it).container; +} +} // namespace detail + +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; + +/** + * An implementation of `std::basic_string_view` for pre-C++17. It provides a + * subset of the API. `fmt::basic_string_view` is used for format strings even + * if `std::basic_string_view` is available to prevent issues when a library is + * compiled with a different `-std` option than the client code (which is not + * recommended). + */ +FMT_EXPORT +template class basic_string_view { + private: + const Char* data_; + size_t size_; + + public: + using value_type = Char; + using iterator = const Char*; + + constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} + + /// Constructs a string reference object from a C string and a size. + constexpr basic_string_view(const Char* s, size_t count) noexcept + : data_(s), size_(count) {} + + constexpr basic_string_view(std::nullptr_t) = delete; + + /// Constructs a string reference object from a C string. + FMT_CONSTEXPR20 + basic_string_view(const Char* s) + : data_(s), + size_(detail::const_check(std::is_same::value && + !detail::is_constant_evaluated(false)) + ? strlen(reinterpret_cast(s)) + : detail::length(s)) {} + + /// Constructs a string reference from a `std::basic_string` or a + /// `std::basic_string_view` object. + template ::value&& std::is_same< + typename S::value_type, Char>::value)> + FMT_CONSTEXPR basic_string_view(const S& s) noexcept + : data_(s.data()), size_(s.size()) {} + + /// Returns a pointer to the string data. + constexpr auto data() const noexcept -> const Char* { return data_; } + + /// Returns the string size. + constexpr auto size() const noexcept -> size_t { return size_; } + + constexpr auto begin() const noexcept -> iterator { return data_; } + constexpr auto end() const noexcept -> iterator { return data_ + size_; } + + constexpr auto operator[](size_t pos) const noexcept -> const Char& { + return data_[pos]; + } + + FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { + data_ += n; + size_ -= n; + } + + FMT_CONSTEXPR auto starts_with(basic_string_view sv) const noexcept + -> bool { + return size_ >= sv.size_ && detail::compare(data_, sv.data_, sv.size_) == 0; + } + FMT_CONSTEXPR auto starts_with(Char c) const noexcept -> bool { + return size_ >= 1 && *data_ == c; + } + FMT_CONSTEXPR auto starts_with(const Char* s) const -> bool { + return starts_with(basic_string_view(s)); + } + + // Lexicographically compare this string reference to other. + FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { + size_t str_size = size_ < other.size_ ? size_ : other.size_; + int result = detail::compare(data_, other.data_, str_size); + if (result == 0) + result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + return result; + } + + FMT_CONSTEXPR friend auto operator==(basic_string_view lhs, + basic_string_view rhs) -> bool { + return lhs.compare(rhs) == 0; + } + friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) != 0; + } + friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) < 0; + } + friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) <= 0; + } + friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) > 0; + } + friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) >= 0; + } +}; + +FMT_EXPORT +using string_view = basic_string_view; + +/// Specifies if `T` is a character type. Can be specialized by users. +FMT_EXPORT +template struct is_char : std::false_type {}; +template <> struct is_char : std::true_type {}; + +namespace detail { + +// Constructs fmt::basic_string_view from types implicitly convertible +// to it, deducing Char. Explicitly convertible types such as the ones returned +// from FMT_STRING are intentionally excluded. +template ::value)> +auto to_string_view(const Char* s) -> basic_string_view { + return s; +} +template ::value)> +auto to_string_view(const T& s) -> basic_string_view { + return s; +} +template +constexpr auto to_string_view(basic_string_view s) + -> basic_string_view { + return s; +} + +template +struct has_to_string_view : std::false_type {}; +// detail:: is intentional since to_string_view is not an extension point. +template +struct has_to_string_view< + T, void_t()))>> + : std::true_type {}; + +template struct string_literal { + static constexpr Char value[sizeof...(C)] = {C...}; + constexpr operator basic_string_view() const { + return {value, sizeof...(C)}; + } +}; +#if FMT_CPLUSPLUS < 201703L +template +constexpr Char string_literal::value[sizeof...(C)]; +#endif + +enum class type { + none_type, + // Integer types should go first, + int_type, + uint_type, + long_long_type, + ulong_long_type, + int128_type, + uint128_type, + bool_type, + char_type, + last_integer_type = char_type, + // followed by floating-point types. + float_type, + double_type, + long_double_type, + last_numeric_type = long_double_type, + cstring_type, + string_type, + pointer_type, + custom_type +}; + +// Maps core type T to the corresponding type enum constant. +template +struct type_constant : std::integral_constant {}; + +#define FMT_TYPE_CONSTANT(Type, constant) \ + template \ + struct type_constant \ + : std::integral_constant {} + +FMT_TYPE_CONSTANT(int, int_type); +FMT_TYPE_CONSTANT(unsigned, uint_type); +FMT_TYPE_CONSTANT(long long, long_long_type); +FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); +FMT_TYPE_CONSTANT(int128_opt, int128_type); +FMT_TYPE_CONSTANT(uint128_opt, uint128_type); +FMT_TYPE_CONSTANT(bool, bool_type); +FMT_TYPE_CONSTANT(Char, char_type); +FMT_TYPE_CONSTANT(float, float_type); +FMT_TYPE_CONSTANT(double, double_type); +FMT_TYPE_CONSTANT(long double, long_double_type); +FMT_TYPE_CONSTANT(const Char*, cstring_type); +FMT_TYPE_CONSTANT(basic_string_view, string_type); +FMT_TYPE_CONSTANT(const void*, pointer_type); + +constexpr auto is_integral_type(type t) -> bool { + return t > type::none_type && t <= type::last_integer_type; +} +constexpr auto is_arithmetic_type(type t) -> bool { + return t > type::none_type && t <= type::last_numeric_type; +} + +constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } +constexpr auto in(type t, int set) -> bool { + return ((set >> static_cast(t)) & 1) != 0; +} + +// Bitsets of types. +enum { + sint_set = + set(type::int_type) | set(type::long_long_type) | set(type::int128_type), + uint_set = set(type::uint_type) | set(type::ulong_long_type) | + set(type::uint128_type), + bool_set = set(type::bool_type), + char_set = set(type::char_type), + float_set = set(type::float_type) | set(type::double_type) | + set(type::long_double_type), + string_set = set(type::string_type), + cstring_set = set(type::cstring_type), + pointer_set = set(type::pointer_type) +}; +} // namespace detail + +/// Reports a format error at compile time or, via a `format_error` exception, +/// at runtime. +// This function is intentionally not constexpr to give a compile-time error. +FMT_NORETURN FMT_API void report_error(const char* message); + +FMT_DEPRECATED FMT_NORETURN inline void throw_format_error( + const char* message) { + report_error(message); +} + +/// String's character (code unit) type. +template ()))> +using char_t = typename V::value_type; + +/** + * Parsing context consisting of a format string range being parsed and an + * argument counter for automatic indexing. + * You can use the `format_parse_context` type alias for `char` instead. + */ +FMT_EXPORT +template class basic_format_parse_context { + private: + basic_string_view format_str_; + int next_arg_id_; + + FMT_CONSTEXPR void do_check_arg_id(int id); + + public: + using char_type = Char; + using iterator = const Char*; + + explicit constexpr basic_format_parse_context( + basic_string_view format_str, int next_arg_id = 0) + : format_str_(format_str), next_arg_id_(next_arg_id) {} + + /// Returns an iterator to the beginning of the format string range being + /// parsed. + constexpr auto begin() const noexcept -> iterator { + return format_str_.begin(); + } + + /// Returns an iterator past the end of the format string range being parsed. + constexpr auto end() const noexcept -> iterator { return format_str_.end(); } + + /// Advances the begin iterator to `it`. + FMT_CONSTEXPR void advance_to(iterator it) { + format_str_.remove_prefix(detail::to_unsigned(it - begin())); + } + + /// Reports an error if using the manual argument indexing; otherwise returns + /// the next argument index and switches to the automatic indexing. + FMT_CONSTEXPR auto next_arg_id() -> int { + if (next_arg_id_ < 0) { + report_error("cannot switch from manual to automatic argument indexing"); + return 0; + } + int id = next_arg_id_++; + do_check_arg_id(id); + return id; + } + + /// Reports an error if using the automatic argument indexing; otherwise + /// switches to the manual indexing. + FMT_CONSTEXPR void check_arg_id(int id) { + if (next_arg_id_ > 0) { + report_error("cannot switch from automatic to manual argument indexing"); + return; + } + next_arg_id_ = -1; + do_check_arg_id(id); + } + FMT_CONSTEXPR void check_arg_id(basic_string_view) { + next_arg_id_ = -1; + } + FMT_CONSTEXPR void check_dynamic_spec(int arg_id); +}; + +FMT_EXPORT +using format_parse_context = basic_format_parse_context; + +namespace detail { +// A parse context with extra data used only in compile-time checks. +template +class compile_parse_context : public basic_format_parse_context { + private: + int num_args_; + const type* types_; + using base = basic_format_parse_context; + + public: + explicit FMT_CONSTEXPR compile_parse_context( + basic_string_view format_str, int num_args, const type* types, + int next_arg_id = 0) + : base(format_str, next_arg_id), num_args_(num_args), types_(types) {} + + constexpr auto num_args() const -> int { return num_args_; } + constexpr auto arg_type(int id) const -> type { return types_[id]; } + + FMT_CONSTEXPR auto next_arg_id() -> int { + int id = base::next_arg_id(); + if (id >= num_args_) report_error("argument not found"); + return id; + } + + FMT_CONSTEXPR void check_arg_id(int id) { + base::check_arg_id(id); + if (id >= num_args_) report_error("argument not found"); + } + using base::check_arg_id; + + FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { + detail::ignore_unused(arg_id); + if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) + report_error("width/precision is not integer"); + } +}; + +/// A contiguous memory buffer with an optional growing ability. It is an +/// internal class and shouldn't be used directly, only via `memory_buffer`. +template class buffer { + private: + T* ptr_; + size_t size_; + size_t capacity_; + + using grow_fun = void (*)(buffer& buf, size_t capacity); + grow_fun grow_; + + protected: + // Don't initialize ptr_ since it is not accessed to save a few cycles. + FMT_MSC_WARNING(suppress : 26495) + FMT_CONSTEXPR20 buffer(grow_fun grow, size_t sz) noexcept + : size_(sz), capacity_(sz), grow_(grow) {} + + constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0, + size_t cap = 0) noexcept + : ptr_(p), size_(sz), capacity_(cap), grow_(grow) {} + + FMT_CONSTEXPR20 ~buffer() = default; + buffer(buffer&&) = default; + + /// Sets the buffer data and capacity. + FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + public: + using value_type = T; + using const_reference = const T&; + + buffer(const buffer&) = delete; + void operator=(const buffer&) = delete; + + auto begin() noexcept -> T* { return ptr_; } + auto end() noexcept -> T* { return ptr_ + size_; } + + auto begin() const noexcept -> const T* { return ptr_; } + auto end() const noexcept -> const T* { return ptr_ + size_; } + + /// Returns the size of this buffer. + constexpr auto size() const noexcept -> size_t { return size_; } + + /// Returns the capacity of this buffer. + constexpr auto capacity() const noexcept -> size_t { return capacity_; } + + /// Returns a pointer to the buffer data (not null-terminated). + FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } + FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } + + /// Clears this buffer. + void clear() { size_ = 0; } + + // Tries resizing the buffer to contain `count` elements. If T is a POD type + // the new elements may not be initialized. + FMT_CONSTEXPR void try_resize(size_t count) { + try_reserve(count); + size_ = count <= capacity_ ? count : capacity_; + } + + // Tries increasing the buffer capacity to `new_capacity`. It can increase the + // capacity by a smaller amount than requested but guarantees there is space + // for at least one additional element either by increasing the capacity or by + // flushing the buffer if it is full. + FMT_CONSTEXPR void try_reserve(size_t new_capacity) { + if (new_capacity > capacity_) grow_(*this, new_capacity); + } + + FMT_CONSTEXPR void push_back(const T& value) { + try_reserve(size_ + 1); + ptr_[size_++] = value; + } + + /// Appends data to the end of the buffer. + template void append(const U* begin, const U* end) { + while (begin != end) { + auto count = to_unsigned(end - begin); + try_reserve(size_ + count); + auto free_cap = capacity_ - size_; + if (free_cap < count) count = free_cap; + if (std::is_same::value) { + memcpy(ptr_ + size_, begin, count * sizeof(T)); + } else { + T* out = ptr_ + size_; + for (size_t i = 0; i < count; ++i) out[i] = begin[i]; + } + size_ += count; + begin += count; + } + } + + template FMT_CONSTEXPR auto operator[](Idx index) -> T& { + return ptr_[index]; + } + template + FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { + return ptr_[index]; + } +}; + +struct buffer_traits { + explicit buffer_traits(size_t) {} + auto count() const -> size_t { return 0; } + auto limit(size_t size) -> size_t { return size; } +}; + +class fixed_buffer_traits { + private: + size_t count_ = 0; + size_t limit_; + + public: + explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} + auto count() const -> size_t { return count_; } + auto limit(size_t size) -> size_t { + size_t n = limit_ > count_ ? limit_ - count_ : 0; + count_ += size; + return size < n ? size : n; + } +}; + +// A buffer that writes to an output iterator when flushed. +template +class iterator_buffer : public Traits, public buffer { + private: + OutputIt out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buffer_size) static_cast(buf).flush(); + } + + void flush() { + auto size = this->size(); + this->clear(); + const T* begin = data_; + const T* end = begin + this->limit(size); + while (begin != end) *out_++ = *begin++; + } + + public: + explicit iterator_buffer(OutputIt out, size_t n = buffer_size) + : Traits(n), buffer(grow, data_, 0, buffer_size), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : Traits(other), + buffer(grow, data_, 0, buffer_size), + out_(other.out_) {} + ~iterator_buffer() { + // Don't crash if flush fails during unwinding. + FMT_TRY { flush(); } + FMT_CATCH(...) {} + } + + auto out() -> OutputIt { + flush(); + return out_; + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; + +template +class iterator_buffer : public fixed_buffer_traits, + public buffer { + private: + T* out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buf.capacity()) + static_cast(buf).flush(); + } + + void flush() { + size_t n = this->limit(this->size()); + if (this->data() == out_) { + out_ += n; + this->set(data_, buffer_size); + } + this->clear(); + } + + public: + explicit iterator_buffer(T* out, size_t n = buffer_size) + : fixed_buffer_traits(n), buffer(grow, out, 0, n), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : fixed_buffer_traits(other), + buffer(static_cast(other)), + out_(other.out_) { + if (this->data() != out_) { + this->set(data_, buffer_size); + this->clear(); + } + } + ~iterator_buffer() { flush(); } + + auto out() -> T* { + flush(); + return out_; + } + auto count() const -> size_t { + return fixed_buffer_traits::count() + this->size(); + } +}; + +template class iterator_buffer : public buffer { + public: + explicit iterator_buffer(T* out, size_t = 0) + : buffer([](buffer&, size_t) {}, out, 0, ~size_t()) {} + + auto out() -> T* { return &*this->end(); } +}; + +// A buffer that writes to a container with the contiguous storage. +template +class iterator_buffer< + OutputIt, + enable_if_t::value && + is_contiguous::value, + typename OutputIt::container_type::value_type>> + : public buffer { + private: + using container_type = typename OutputIt::container_type; + using value_type = typename container_type::value_type; + container_type& container_; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t capacity) { + auto& self = static_cast(buf); + self.container_.resize(capacity); + self.set(&self.container_[0], capacity); + } + + public: + explicit iterator_buffer(container_type& c) + : buffer(grow, c.size()), container_(c) {} + explicit iterator_buffer(OutputIt out, size_t = 0) + : iterator_buffer(get_container(out)) {} + + auto out() -> OutputIt { return back_inserter(container_); } +}; + +// A buffer that counts the number of code units written discarding the output. +template class counting_buffer : public buffer { + private: + enum { buffer_size = 256 }; + T data_[buffer_size]; + size_t count_ = 0; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() != buffer_size) return; + static_cast(buf).count_ += buf.size(); + buf.clear(); + } + + public: + counting_buffer() : buffer(grow, data_, 0, buffer_size) {} + + auto count() -> size_t { return count_ + this->size(); } +}; +} // namespace detail + +template +FMT_CONSTEXPR void basic_format_parse_context::do_check_arg_id(int id) { + // Argument id is only checked at compile-time during parsing because + // formatting has its own validation. + if (detail::is_constant_evaluated() && + (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { + using context = detail::compile_parse_context; + if (id >= static_cast(this)->num_args()) + report_error("argument not found"); + } +} + +template +FMT_CONSTEXPR void basic_format_parse_context::check_dynamic_spec( + int arg_id) { + if (detail::is_constant_evaluated() && + (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { + using context = detail::compile_parse_context; + static_cast(this)->check_dynamic_spec(arg_id); + } +} + +FMT_EXPORT template class basic_format_arg; +FMT_EXPORT template class basic_format_args; +FMT_EXPORT template class dynamic_format_arg_store; + +// A formatter for objects of type T. +FMT_EXPORT +template +struct formatter { + // A deleted default constructor indicates a disabled formatter. + formatter() = delete; +}; + +// Specifies if T has an enabled formatter specialization. A type can be +// formattable even if it doesn't have a formatter e.g. via a conversion. +template +using has_formatter = + std::is_constructible>; + +// An output iterator that appends to a buffer. It is used instead of +// back_insert_iterator to reduce symbol sizes and avoid dependency. +template class basic_appender { + private: + detail::buffer* buffer_; + + friend auto get_container(basic_appender app) -> detail::buffer& { + return *app.buffer_; + } + + public: + using iterator_category = int; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; + FMT_UNCHECKED_ITERATOR(basic_appender); + + FMT_CONSTEXPR basic_appender(detail::buffer& buf) : buffer_(&buf) {} + + auto operator=(T c) -> basic_appender& { + buffer_->push_back(c); + return *this; + } + auto operator*() -> basic_appender& { return *this; } + auto operator++() -> basic_appender& { return *this; } + auto operator++(int) -> basic_appender { return *this; } +}; + +using appender = basic_appender; + +namespace detail { + +template +struct locking : std::true_type {}; +template +struct locking>::nonlocking>> + : std::false_type {}; + +template FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value; +} +template +FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value || is_locking(); +} + +// An optimized version of std::copy with the output value type (T). +template +auto copy(InputIt begin, InputIt end, appender out) -> appender { + get_container(out).append(begin, end); + return out; +} + +template ::value)> +auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + get_container(out).append(begin, end); + return out; +} + +template ::value)> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + while (begin != end) *out++ = static_cast(*begin++); + return out; +} + +template +FMT_CONSTEXPR auto copy(const T* begin, const T* end, T* out) -> T* { + if (is_constant_evaluated()) return copy(begin, end, out); + auto size = to_unsigned(end - begin); + if (size > 0) memcpy(out, begin, size * sizeof(T)); + return out + size; +} + +template +FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { + return copy(s.begin(), s.end(), out); +} + +template +constexpr auto has_const_formatter_impl(T*) + -> decltype(typename Context::template formatter_type().format( + std::declval(), std::declval()), + true) { + return true; +} +template +constexpr auto has_const_formatter_impl(...) -> bool { + return false; +} +template +constexpr auto has_const_formatter() -> bool { + return has_const_formatter_impl(static_cast(nullptr)); +} + +// Maps an output iterator to a buffer. +template +auto get_buffer(OutputIt out) -> iterator_buffer { + return iterator_buffer(out); +} +template auto get_buffer(basic_appender out) -> buffer& { + return get_container(out); +} + +template +auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { + return buf.out(); +} +template +auto get_iterator(buffer&, OutputIt out) -> OutputIt { + return out; +} + +struct view {}; + +template struct named_arg : view { + const Char* name; + const T& value; + named_arg(const Char* n, const T& v) : name(n), value(v) {} +}; + +template struct named_arg_info { + const Char* name; + int id; +}; + +template struct is_named_arg : std::false_type {}; +template struct is_statically_named_arg : std::false_type {}; + +template +struct is_named_arg> : std::true_type {}; + +template constexpr auto count() -> size_t { return B ? 1 : 0; } +template constexpr auto count() -> size_t { + return (B1 ? 1 : 0) + count(); +} + +template constexpr auto count_named_args() -> size_t { + return count::value...>(); +} + +template +constexpr auto count_statically_named_args() -> size_t { + return count::value...>(); +} + +struct unformattable {}; +struct unformattable_char : unformattable {}; +struct unformattable_pointer : unformattable {}; + +template struct string_value { + const Char* data; + size_t size; +}; + +template struct named_arg_value { + const named_arg_info* data; + size_t size; +}; + +template struct custom_value { + using parse_context = typename Context::parse_context_type; + void* value; + void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); +}; + +// A formatting argument value. +template class value { + public: + using char_type = typename Context::char_type; + + union { + monostate no_value; + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + int128_opt int128_value; + uint128_opt uint128_value; + bool bool_value; + char_type char_value; + float float_value; + double double_value; + long double long_double_value; + const void* pointer; + string_value string; + custom_value custom; + named_arg_value named_args; + }; + + constexpr FMT_ALWAYS_INLINE value() : no_value() {} + constexpr FMT_ALWAYS_INLINE value(int val) : int_value(val) {} + constexpr FMT_ALWAYS_INLINE value(unsigned val) : uint_value(val) {} + constexpr FMT_ALWAYS_INLINE value(long long val) : long_long_value(val) {} + constexpr FMT_ALWAYS_INLINE value(unsigned long long val) + : ulong_long_value(val) {} + FMT_ALWAYS_INLINE value(int128_opt val) : int128_value(val) {} + FMT_ALWAYS_INLINE value(uint128_opt val) : uint128_value(val) {} + constexpr FMT_ALWAYS_INLINE value(float val) : float_value(val) {} + constexpr FMT_ALWAYS_INLINE value(double val) : double_value(val) {} + FMT_ALWAYS_INLINE value(long double val) : long_double_value(val) {} + constexpr FMT_ALWAYS_INLINE value(bool val) : bool_value(val) {} + constexpr FMT_ALWAYS_INLINE value(char_type val) : char_value(val) {} + FMT_CONSTEXPR FMT_ALWAYS_INLINE value(const char_type* val) { + string.data = val; + if (is_constant_evaluated()) string.size = {}; + } + FMT_CONSTEXPR FMT_ALWAYS_INLINE value(basic_string_view val) { + string.data = val.data(); + string.size = val.size(); + } + FMT_ALWAYS_INLINE value(const void* val) : pointer(val) {} + FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) + : named_args{args, size} {} + + template FMT_CONSTEXPR20 FMT_ALWAYS_INLINE value(T& val) { + using value_type = remove_const_t; + // T may overload operator& e.g. std::vector::reference in libc++. +#if defined(__cpp_if_constexpr) + if constexpr (std::is_same::value) + custom.value = const_cast(&val); +#endif + if (!is_constant_evaluated()) + custom.value = const_cast(&reinterpret_cast(val)); + // Get the formatter type through the context to allow different contexts + // have different extension points, e.g. `formatter` for `format` and + // `printf_formatter` for `printf`. + custom.format = format_custom_arg< + value_type, typename Context::template formatter_type>; + } + value(unformattable); + value(unformattable_char); + value(unformattable_pointer); + + private: + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom_arg(void* arg, + typename Context::parse_context_type& parse_ctx, + Context& ctx) { + auto f = Formatter(); + parse_ctx.advance_to(f.parse(parse_ctx)); + using qualified_type = + conditional_t(), const T, T>; + // format must be const for compatibility with std::format and compilation. + const auto& cf = f; + ctx.advance_to(cf.format(*static_cast(arg), ctx)); + } +}; + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +enum { long_short = sizeof(long) == sizeof(int) }; +using long_type = conditional_t; +using ulong_type = conditional_t; + +template struct format_as_result { + template ::value || std::is_class::value)> + static auto map(U*) -> remove_cvref_t()))>; + static auto map(...) -> void; + + using type = decltype(map(static_cast(nullptr))); +}; +template using format_as_t = typename format_as_result::type; + +template +struct has_format_as + : bool_constant, void>::value> {}; + +#define FMT_MAP_API FMT_CONSTEXPR FMT_ALWAYS_INLINE + +// Maps formatting arguments to core types. +// arg_mapper reports errors by returning unformattable instead of using +// static_assert because it's used in the is_formattable trait. +template struct arg_mapper { + using char_type = typename Context::char_type; + + FMT_MAP_API auto map(signed char val) -> int { return val; } + FMT_MAP_API auto map(unsigned char val) -> unsigned { return val; } + FMT_MAP_API auto map(short val) -> int { return val; } + FMT_MAP_API auto map(unsigned short val) -> unsigned { return val; } + FMT_MAP_API auto map(int val) -> int { return val; } + FMT_MAP_API auto map(unsigned val) -> unsigned { return val; } + FMT_MAP_API auto map(long val) -> long_type { return val; } + FMT_MAP_API auto map(unsigned long val) -> ulong_type { return val; } + FMT_MAP_API auto map(long long val) -> long long { return val; } + FMT_MAP_API auto map(unsigned long long val) -> unsigned long long { + return val; + } + FMT_MAP_API auto map(int128_opt val) -> int128_opt { return val; } + FMT_MAP_API auto map(uint128_opt val) -> uint128_opt { return val; } + FMT_MAP_API auto map(bool val) -> bool { return val; } + + template ::value || + std::is_same::value)> + FMT_MAP_API auto map(T val) -> char_type { + return val; + } + template ::value || +#ifdef __cpp_char8_t + std::is_same::value || +#endif + std::is_same::value || + std::is_same::value) && + !std::is_same::value, + int> = 0> + FMT_MAP_API auto map(T) -> unformattable_char { + return {}; + } + + FMT_MAP_API auto map(float val) -> float { return val; } + FMT_MAP_API auto map(double val) -> double { return val; } + FMT_MAP_API auto map(long double val) -> long double { return val; } + + FMT_MAP_API auto map(char_type* val) -> const char_type* { return val; } + FMT_MAP_API auto map(const char_type* val) -> const char_type* { return val; } + template , + FMT_ENABLE_IF(std::is_same::value && + !std::is_pointer::value)> + FMT_MAP_API auto map(const T& val) -> basic_string_view { + return to_string_view(val); + } + template , + FMT_ENABLE_IF(!std::is_same::value && + !std::is_pointer::value)> + FMT_MAP_API auto map(const T&) -> unformattable_char { + return {}; + } + + FMT_MAP_API auto map(void* val) -> const void* { return val; } + FMT_MAP_API auto map(const void* val) -> const void* { return val; } + FMT_MAP_API auto map(std::nullptr_t val) -> const void* { return val; } + + // Use SFINAE instead of a const T* parameter to avoid a conflict with the + // array overload. + template < + typename T, + FMT_ENABLE_IF( + std::is_pointer::value || std::is_member_pointer::value || + std::is_function::type>::value || + (std::is_array::value && + !std::is_convertible::value))> + FMT_CONSTEXPR auto map(const T&) -> unformattable_pointer { + return {}; + } + + template ::value)> + FMT_MAP_API auto map(const T (&values)[N]) -> const T (&)[N] { + return values; + } + + // Only map owning types because mapping views can be unsafe. + template , + FMT_ENABLE_IF(std::is_arithmetic::value)> + FMT_MAP_API auto map(const T& val) -> decltype(FMT_DECLTYPE_THIS map(U())) { + return map(format_as(val)); + } + + template > + struct formattable : bool_constant() || + (has_formatter::value && + !std::is_const::value)> {}; + + template ::value)> + FMT_MAP_API auto do_map(T& val) -> T& { + return val; + } + template ::value)> + FMT_MAP_API auto do_map(T&) -> unformattable { + return {}; + } + + // is_fundamental is used to allow formatters for extended FP types. + template , + FMT_ENABLE_IF( + (std::is_class::value || std::is_enum::value || + std::is_union::value || std::is_fundamental::value) && + !has_to_string_view::value && !is_char::value && + !is_named_arg::value && !std::is_integral::value && + !std::is_arithmetic>::value)> + FMT_MAP_API auto map(T& val) -> decltype(FMT_DECLTYPE_THIS do_map(val)) { + return do_map(val); + } + + template ::value)> + FMT_MAP_API auto map(const T& named_arg) + -> decltype(FMT_DECLTYPE_THIS map(named_arg.value)) { + return map(named_arg.value); + } + + auto map(...) -> unformattable { return {}; } +}; + +// A type constant after applying arg_mapper. +template +using mapped_type_constant = + type_constant().map(std::declval())), + typename Context::char_type>; + +enum { packed_arg_bits = 4 }; +// Maximum number of arguments with packed types. +enum { max_packed_args = 62 / packed_arg_bits }; +enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; +enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; + +template +struct is_output_iterator : std::false_type {}; + +template <> struct is_output_iterator : std::true_type {}; + +template +struct is_output_iterator< + It, T, void_t()++ = std::declval())>> + : std::true_type {}; + +// A type-erased reference to an std::locale to avoid a heavy include. +class locale_ref { + private: + const void* locale_; // A type-erased pointer to std::locale. + + public: + constexpr locale_ref() : locale_(nullptr) {} + template explicit locale_ref(const Locale& loc); + + explicit operator bool() const noexcept { return locale_ != nullptr; } + + template auto get() const -> Locale; +}; + +template constexpr auto encode_types() -> unsigned long long { + return 0; +} + +template +constexpr auto encode_types() -> unsigned long long { + return static_cast(mapped_type_constant::value) | + (encode_types() << packed_arg_bits); +} + +template +constexpr unsigned long long make_descriptor() { + return NUM_ARGS <= max_packed_args ? encode_types() + : is_unpacked_bit | NUM_ARGS; +} + +// This type is intentionally undefined, only used for errors. +template +#if FMT_CLANG_VERSION && FMT_CLANG_VERSION <= 1500 +// https://github.com/fmtlib/fmt/issues/3796 +struct type_is_unformattable_for { +}; +#else +struct type_is_unformattable_for; +#endif + +template +FMT_CONSTEXPR auto make_arg(T& val) -> value { + using arg_type = remove_cvref_t().map(val))>; + + // Use enum instead of constexpr because the latter may generate code. + enum { + formattable_char = !std::is_same::value + }; + static_assert(formattable_char, "Mixing character types is disallowed."); + + // Formatting of arbitrary pointers is disallowed. If you want to format a + // pointer cast it to `void*` or `const void*`. In particular, this forbids + // formatting of `[const] volatile char*` printed as bool by iostreams. + enum { + formattable_pointer = !std::is_same::value + }; + static_assert(formattable_pointer, + "Formatting of non-void pointers is disallowed."); + + enum { formattable = !std::is_same::value }; +#if defined(__cpp_if_constexpr) + if constexpr (!formattable) + type_is_unformattable_for _; +#endif + static_assert( + formattable, + "Cannot format an argument. To make type T formattable provide a " + "formatter specialization: https://fmt.dev/latest/api.html#udt"); + return {arg_mapper().map(val)}; +} + +template +FMT_CONSTEXPR auto make_arg(T& val) -> basic_format_arg { + auto arg = basic_format_arg(); + arg.type_ = mapped_type_constant::value; + arg.value_ = make_arg(val); + return arg; +} + +template +FMT_CONSTEXPR inline auto make_arg(T& val) -> basic_format_arg { + return make_arg(val); +} + +template +using arg_t = conditional_t, + basic_format_arg>; + +template ::value)> +void init_named_arg(named_arg_info*, int& arg_index, int&, const T&) { + ++arg_index; +} +template ::value)> +void init_named_arg(named_arg_info* named_args, int& arg_index, + int& named_arg_index, const T& arg) { + named_args[named_arg_index++] = {arg.name, arg_index++}; +} + +// An array of references to arguments. It can be implicitly converted to +// `fmt::basic_format_args` for passing into type-erased formatting functions +// such as `fmt::vformat`. +template +struct format_arg_store { + // args_[0].named_args points to named_args to avoid bloating format_args. + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + static constexpr size_t ARGS_ARR_SIZE = 1 + (NUM_ARGS != 0 ? NUM_ARGS : +1); + + arg_t args[ARGS_ARR_SIZE]; + named_arg_info named_args[NUM_NAMED_ARGS]; + + template + FMT_MAP_API format_arg_store(T&... values) + : args{{named_args, NUM_NAMED_ARGS}, + make_arg(values)...} { + using dummy = int[]; + int arg_index = 0, named_arg_index = 0; + (void)dummy{ + 0, + (init_named_arg(named_args, arg_index, named_arg_index, values), 0)...}; + } + + format_arg_store(format_arg_store&& rhs) { + args[0] = {named_args, NUM_NAMED_ARGS}; + for (size_t i = 1; i < ARGS_ARR_SIZE; ++i) args[i] = rhs.args[i]; + for (size_t i = 0; i < NUM_NAMED_ARGS; ++i) + named_args[i] = rhs.named_args[i]; + } + + format_arg_store(const format_arg_store& rhs) = delete; + format_arg_store& operator=(const format_arg_store& rhs) = delete; + format_arg_store& operator=(format_arg_store&& rhs) = delete; +}; + +// A specialization of format_arg_store without named arguments. +// It is a plain struct to reduce binary size in debug mode. +template +struct format_arg_store { + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + arg_t args[NUM_ARGS != 0 ? NUM_ARGS : +1]; +}; + +} // namespace detail +FMT_BEGIN_EXPORT + +// A formatting argument. Context is a template parameter for the compiled API +// where output can be unbuffered. +template class basic_format_arg { + private: + detail::value value_; + detail::type type_; + + template + friend FMT_CONSTEXPR auto detail::make_arg(T& value) + -> basic_format_arg; + + friend class basic_format_args; + friend class dynamic_format_arg_store; + + using char_type = typename Context::char_type; + + template + friend struct detail::format_arg_store; + + basic_format_arg(const detail::named_arg_info* args, size_t size) + : value_(args, size) {} + + public: + class handle { + public: + explicit handle(detail::custom_value custom) : custom_(custom) {} + + void format(typename Context::parse_context_type& parse_ctx, + Context& ctx) const { + custom_.format(custom_.value, parse_ctx, ctx); + } + + private: + detail::custom_value custom_; + }; + + constexpr basic_format_arg() : type_(detail::type::none_type) {} + + constexpr explicit operator bool() const noexcept { + return type_ != detail::type::none_type; + } + + auto type() const -> detail::type { return type_; } + + auto is_integral() const -> bool { return detail::is_integral_type(type_); } + auto is_arithmetic() const -> bool { + return detail::is_arithmetic_type(type_); + } + + /** + * Visits an argument dispatching to the appropriate visit method based on + * the argument type. For example, if the argument type is `double` then + * `vis(value)` will be called with the value of type `double`. + */ + template + FMT_CONSTEXPR auto visit(Visitor&& vis) -> decltype(vis(0)) { + switch (type_) { + case detail::type::none_type: + break; + case detail::type::int_type: + return vis(value_.int_value); + case detail::type::uint_type: + return vis(value_.uint_value); + case detail::type::long_long_type: + return vis(value_.long_long_value); + case detail::type::ulong_long_type: + return vis(value_.ulong_long_value); + case detail::type::int128_type: + return vis(detail::convert_for_visit(value_.int128_value)); + case detail::type::uint128_type: + return vis(detail::convert_for_visit(value_.uint128_value)); + case detail::type::bool_type: + return vis(value_.bool_value); + case detail::type::char_type: + return vis(value_.char_value); + case detail::type::float_type: + return vis(value_.float_value); + case detail::type::double_type: + return vis(value_.double_value); + case detail::type::long_double_type: + return vis(value_.long_double_value); + case detail::type::cstring_type: + return vis(value_.string.data); + case detail::type::string_type: + using sv = basic_string_view; + return vis(sv(value_.string.data, value_.string.size)); + case detail::type::pointer_type: + return vis(value_.pointer); + case detail::type::custom_type: + return vis(typename basic_format_arg::handle(value_.custom)); + } + return vis(monostate()); + } + + auto format_custom(const char_type* parse_begin, + typename Context::parse_context_type& parse_ctx, + Context& ctx) -> bool { + if (type_ != detail::type::custom_type) return false; + parse_ctx.advance_to(parse_begin); + value_.custom.format(value_.custom.value, parse_ctx, ctx); + return true; + } +}; + +template +FMT_DEPRECATED FMT_CONSTEXPR auto visit_format_arg( + Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { + return arg.visit(static_cast(vis)); +} + +/** + * A view of a collection of formatting arguments. To avoid lifetime issues it + * should only be used as a parameter type in type-erased functions such as + * `vformat`: + * + * void vlog(fmt::string_view fmt, fmt::format_args args); // OK + * fmt::format_args args = fmt::make_format_args(); // Dangling reference + */ +template class basic_format_args { + public: + using size_type = int; + using format_arg = basic_format_arg; + + private: + // A descriptor that contains information about formatting arguments. + // If the number of arguments is less or equal to max_packed_args then + // argument types are passed in the descriptor. This reduces binary code size + // per formatting function call. + unsigned long long desc_; + union { + // If is_packed() returns true then argument values are stored in values_; + // otherwise they are stored in args_. This is done to improve cache + // locality and reduce compiled code size since storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const detail::value* values_; + const format_arg* args_; + }; + + constexpr auto is_packed() const -> bool { + return (desc_ & detail::is_unpacked_bit) == 0; + } + constexpr auto has_named_args() const -> bool { + return (desc_ & detail::has_named_args_bit) != 0; + } + + FMT_CONSTEXPR auto type(int index) const -> detail::type { + int shift = index * detail::packed_arg_bits; + unsigned int mask = (1 << detail::packed_arg_bits) - 1; + return static_cast((desc_ >> shift) & mask); + } + + public: + constexpr basic_format_args() : desc_(0), args_(nullptr) {} + + /// Constructs a `basic_format_args` object from `format_arg_store`. + template + constexpr FMT_ALWAYS_INLINE basic_format_args( + const detail::format_arg_store& + store) + : desc_(DESC), values_(store.args + (NUM_NAMED_ARGS != 0 ? 1 : 0)) {} + + template detail::max_packed_args)> + constexpr basic_format_args( + const detail::format_arg_store& + store) + : desc_(DESC), args_(store.args + (NUM_NAMED_ARGS != 0 ? 1 : 0)) {} + + /// Constructs a `basic_format_args` object from `dynamic_format_arg_store`. + constexpr basic_format_args(const dynamic_format_arg_store& store) + : desc_(store.get_types()), args_(store.data()) {} + + /// Constructs a `basic_format_args` object from a dynamic list of arguments. + constexpr basic_format_args(const format_arg* args, int count) + : desc_(detail::is_unpacked_bit | detail::to_unsigned(count)), + args_(args) {} + + /// Returns the argument with the specified id. + FMT_CONSTEXPR auto get(int id) const -> format_arg { + format_arg arg; + if (!is_packed()) { + if (id < max_size()) arg = args_[id]; + return arg; + } + if (static_cast(id) >= detail::max_packed_args) return arg; + arg.type_ = type(id); + if (arg.type_ == detail::type::none_type) return arg; + arg.value_ = values_[id]; + return arg; + } + + template + auto get(basic_string_view name) const -> format_arg { + int id = get_id(name); + return id >= 0 ? get(id) : format_arg(); + } + + template + FMT_CONSTEXPR auto get_id(basic_string_view name) const -> int { + if (!has_named_args()) return -1; + const auto& named_args = + (is_packed() ? values_[-1] : args_[-1].value_).named_args; + for (size_t i = 0; i < named_args.size; ++i) { + if (named_args.data[i].name == name) return named_args.data[i].id; + } + return -1; + } + + auto max_size() const -> int { + unsigned long long max_packed = detail::max_packed_args; + return static_cast(is_packed() ? max_packed + : desc_ & ~detail::is_unpacked_bit); + } +}; + +// A formatting context. +class context { + private: + appender out_; + basic_format_args args_; + detail::locale_ref loc_; + + public: + /// The character type for the output. + using char_type = char; + + using iterator = appender; + using format_arg = basic_format_arg; + using parse_context_type = basic_format_parse_context; + template using formatter_type = formatter; + + /// Constructs a `basic_format_context` object. References to the arguments + /// are stored in the object so make sure they have appropriate lifetimes. + FMT_CONSTEXPR context(iterator out, basic_format_args ctx_args, + detail::locale_ref loc = {}) + : out_(out), args_(ctx_args), loc_(loc) {} + context(context&&) = default; + context(const context&) = delete; + void operator=(const context&) = delete; + + FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); } + auto arg(string_view name) -> format_arg { return args_.get(name); } + FMT_CONSTEXPR auto arg_id(string_view name) -> int { + return args_.get_id(name); + } + auto args() const -> const basic_format_args& { return args_; } + + // Returns an iterator to the beginning of the output range. + FMT_CONSTEXPR auto out() -> iterator { return out_; } + + // Advances the begin iterator to `it`. + void advance_to(iterator) {} + + FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } +}; + +template class generic_context; + +// Longer aliases for C++20 compatibility. +template +using basic_format_context = + conditional_t::value, context, + generic_context>; +using format_context = context; + +template +using buffered_context = basic_format_context, Char>; + +template +using is_formattable = bool_constant>() + .map(std::declval()))>::value>; + +#if FMT_USE_CONCEPTS +template +concept formattable = is_formattable, Char>::value; +#endif + +/** + * Constructs an object that stores references to arguments and can be + * implicitly converted to `format_args`. `Context` can be omitted in which case + * it defaults to `format_context`. See `arg` for lifetime considerations. + */ +// Take arguments by lvalue references to avoid some lifetime issues, e.g. +// auto args = make_format_args(std::string()); +template (), + unsigned long long DESC = detail::make_descriptor(), + FMT_ENABLE_IF(NUM_NAMED_ARGS == 0)> +constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args) + -> detail::format_arg_store { + return {{detail::make_arg( + args)...}}; +} + +#ifndef FMT_DOC +template (), + unsigned long long DESC = + detail::make_descriptor() | + static_cast(detail::has_named_args_bit), + FMT_ENABLE_IF(NUM_NAMED_ARGS != 0)> +constexpr auto make_format_args(T&... args) + -> detail::format_arg_store { + return {args...}; +} +#endif + +/** + * Returns a named argument to be used in a formatting function. + * It should only be used in a call to a formatting function or + * `dynamic_format_arg_store::push_back`. + * + * **Example**: + * + * fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); + */ +template +inline auto arg(const Char* name, const T& arg) -> detail::named_arg { + static_assert(!detail::is_named_arg(), "nested named arguments"); + return {name, arg}; +} +FMT_END_EXPORT + +/// An alias for `basic_format_args`. +// A separate type would result in shorter symbols but break ABI compatibility +// between clang and gcc on ARM (#1919). +FMT_EXPORT using format_args = basic_format_args; + +// We cannot use enum classes as bit fields because of a gcc bug, so we put them +// in namespaces instead (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414). +// Additionally, if an underlying type is specified, older gcc incorrectly warns +// that the type is too small. Both bugs are fixed in gcc 9.3. +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 903 +# define FMT_ENUM_UNDERLYING_TYPE(type) +#else +# define FMT_ENUM_UNDERLYING_TYPE(type) : type +#endif +namespace align { +enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, left, right, center, + numeric}; +} +using align_t = align::type; +namespace sign { +enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, minus, plus, space}; +} +using sign_t = sign::type; + +namespace detail { + +template +using unsigned_char = typename conditional_t::value, + std::make_unsigned, + type_identity>::type; + +// Character (code unit) type is erased to prevent template bloat. +struct fill_t { + private: + enum { max_size = 4 }; + char data_[max_size] = {' '}; + unsigned char size_ = 1; + + public: + template + FMT_CONSTEXPR void operator=(basic_string_view s) { + auto size = s.size(); + size_ = static_cast(size); + if (size == 1) { + unsigned uchar = static_cast>(s[0]); + data_[0] = static_cast(uchar); + data_[1] = static_cast(uchar >> 8); + return; + } + FMT_ASSERT(size <= max_size, "invalid fill"); + for (size_t i = 0; i < size; ++i) data_[i] = static_cast(s[i]); + } + + FMT_CONSTEXPR void operator=(char c) { + data_[0] = c; + size_ = 1; + } + + constexpr auto size() const -> size_t { return size_; } + + template constexpr auto get() const -> Char { + using uchar = unsigned char; + return static_cast(static_cast(data_[0]) | + (static_cast(data_[1]) << 8)); + } + + template ::value)> + constexpr auto data() const -> const Char* { + return data_; + } + template ::value)> + constexpr auto data() const -> const Char* { + return nullptr; + } +}; +} // namespace detail + +enum class presentation_type : unsigned char { + // Common specifiers: + none = 0, + debug = 1, // '?' + string = 2, // 's' (string, bool) + + // Integral, bool and character specifiers: + dec = 3, // 'd' + hex, // 'x' or 'X' + oct, // 'o' + bin, // 'b' or 'B' + chr, // 'c' + + // String and pointer specifiers: + pointer = 3, // 'p' + + // Floating-point specifiers: + exp = 1, // 'e' or 'E' (1 since there is no FP debug presentation) + fixed, // 'f' or 'F' + general, // 'g' or 'G' + hexfloat // 'a' or 'A' +}; + +// Format specifiers for built-in and string types. +struct format_specs { + int width; + int precision; + presentation_type type; + align_t align : 4; + sign_t sign : 3; + bool upper : 1; // An uppercase version e.g. 'X' for 'x'. + bool alt : 1; // Alternate form ('#'). + bool localized : 1; + detail::fill_t fill; + + constexpr format_specs() + : width(0), + precision(-1), + type(presentation_type::none), + align(align::none), + sign(sign::none), + upper(false), + alt(false), + localized(false) {} +}; + +namespace detail { + +enum class arg_id_kind { none, index, name }; + +// An argument reference. +template struct arg_ref { + FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} + + FMT_CONSTEXPR explicit arg_ref(int index) + : kind(arg_id_kind::index), val(index) {} + FMT_CONSTEXPR explicit arg_ref(basic_string_view name) + : kind(arg_id_kind::name), val(name) {} + + FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& { + kind = arg_id_kind::index; + val.index = idx; + return *this; + } + + arg_id_kind kind; + union value { + FMT_CONSTEXPR value(int idx = 0) : index(idx) {} + FMT_CONSTEXPR value(basic_string_view n) : name(n) {} + + int index; + basic_string_view name; + } val; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow reusing the same parsed specifiers with +// different sets of arguments (precompilation of format strings). +template struct dynamic_format_specs : format_specs { + arg_ref width_ref; + arg_ref precision_ref; +}; + +// Converts a character to ASCII. Returns '\0' on conversion failure. +template ::value)> +constexpr auto to_ascii(Char c) -> char { + return c <= 0xff ? static_cast(c) : '\0'; +} + +// Returns the number of code units in a code point or 1 on error. +template +FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { + if (const_check(sizeof(Char) != 1)) return 1; + auto c = static_cast(*begin); + return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 0x3) + 1; +} + +// Return the result via the out param to workaround gcc bug 77539. +template +FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { + for (out = first; out != last; ++out) { + if (*out == value) return true; + } + return false; +} + +template <> +inline auto find(const char* first, const char* last, char value, + const char*& out) -> bool { + out = + static_cast(memchr(first, value, to_unsigned(last - first))); + return out != nullptr; +} + +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template +FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, + int error_value) noexcept -> int { + FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); + unsigned value = 0, prev = 0; + auto p = begin; + do { + prev = value; + value = value * 10 + unsigned(*p - '0'); + ++p; + } while (p != end && '0' <= *p && *p <= '9'); + auto num_digits = p - begin; + begin = p; + int digits10 = static_cast(sizeof(int) * CHAR_BIT * 3 / 10); + if (num_digits <= digits10) return static_cast(value); + // Check for overflow. + unsigned max = INT_MAX; + return num_digits == digits10 + 1 && + prev * 10ull + unsigned(p[-1] - '0') <= max + ? static_cast(value) + : error_value; +} + +FMT_CONSTEXPR inline auto parse_align(char c) -> align_t { + switch (c) { + case '<': + return align::left; + case '>': + return align::right; + case '^': + return align::center; + } + return align::none; +} + +template constexpr auto is_name_start(Char c) -> bool { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; +} + +template +FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + Char c = *begin; + if (c >= '0' && c <= '9') { + int index = 0; + if (c != '0') + index = parse_nonnegative_int(begin, end, INT_MAX); + else + ++begin; + if (begin == end || (*begin != '}' && *begin != ':')) + report_error("invalid format string"); + else + handler.on_index(index); + return begin; + } + if (!is_name_start(c)) { + report_error("invalid format string"); + return begin; + } + auto it = begin; + do { + ++it; + } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); + handler.on_name({begin, to_unsigned(it - begin)}); + return it; +} + +template +FMT_CONSTEXPR auto parse_arg_id(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); + Char c = *begin; + if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); + handler.on_auto(); + return begin; +} + +template struct dynamic_spec_id_handler { + basic_format_parse_context& ctx; + arg_ref& ref; + + FMT_CONSTEXPR void on_auto() { + int id = ctx.next_arg_id(); + ref = arg_ref(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_index(int id) { + ref = arg_ref(id); + ctx.check_arg_id(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_name(basic_string_view id) { + ref = arg_ref(id); + ctx.check_arg_id(id); + } +}; + +// Parses [integer | "{" [arg_id] "}"]. +template +FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, + int& value, arg_ref& ref, + basic_format_parse_context& ctx) + -> const Char* { + FMT_ASSERT(begin != end, ""); + if ('0' <= *begin && *begin <= '9') { + int val = parse_nonnegative_int(begin, end, -1); + if (val != -1) + value = val; + else + report_error("number is too big"); + } else if (*begin == '{') { + ++begin; + auto handler = dynamic_spec_id_handler{ctx, ref}; + if (begin != end) begin = parse_arg_id(begin, end, handler); + if (begin != end && *begin == '}') return ++begin; + report_error("invalid format string"); + } + return begin; +} + +template +FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, + int& value, arg_ref& ref, + basic_format_parse_context& ctx) + -> const Char* { + ++begin; + if (begin == end || *begin == '}') { + report_error("invalid precision"); + return begin; + } + return parse_dynamic_spec(begin, end, value, ref, ctx); +} + +enum class state { start, align, sign, hash, zero, width, precision, locale }; + +// Parses standard format specifiers. +template +FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, + dynamic_format_specs& specs, + basic_format_parse_context& ctx, + type arg_type) -> const Char* { + auto c = '\0'; + if (end - begin > 1) { + auto next = to_ascii(begin[1]); + c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; + } else { + if (begin == end) return begin; + c = to_ascii(*begin); + } + + struct { + state current_state = state::start; + FMT_CONSTEXPR void operator()(state s, bool valid = true) { + if (current_state >= s || !valid) + report_error("invalid format specifier"); + current_state = s; + } + } enter_state; + + using pres = presentation_type; + constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; + struct { + const Char*& begin; + dynamic_format_specs& specs; + type arg_type; + + FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* { + if (!in(arg_type, set)) { + if (arg_type == type::none_type) return begin; + report_error("invalid format specifier"); + } + specs.type = pres_type; + return begin + 1; + } + } parse_presentation_type{begin, specs, arg_type}; + + for (;;) { + switch (c) { + case '<': + case '>': + case '^': + enter_state(state::align); + specs.align = parse_align(c); + ++begin; + break; + case '+': + case '-': + case ' ': + if (arg_type == type::none_type) return begin; + enter_state(state::sign, in(arg_type, sint_set | float_set)); + switch (c) { + case '+': + specs.sign = sign::plus; + break; + case '-': + specs.sign = sign::minus; + break; + case ' ': + specs.sign = sign::space; + break; + } + ++begin; + break; + case '#': + if (arg_type == type::none_type) return begin; + enter_state(state::hash, is_arithmetic_type(arg_type)); + specs.alt = true; + ++begin; + break; + case '0': + enter_state(state::zero); + if (!is_arithmetic_type(arg_type)) { + if (arg_type == type::none_type) return begin; + report_error("format specifier requires numeric argument"); + } + if (specs.align == align::none) { + // Ignore 0 if align is specified for compatibility with std::format. + specs.align = align::numeric; + specs.fill = '0'; + } + ++begin; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '{': + enter_state(state::width); + begin = parse_dynamic_spec(begin, end, specs.width, specs.width_ref, ctx); + break; + case '.': + if (arg_type == type::none_type) return begin; + enter_state(state::precision, + in(arg_type, float_set | string_set | cstring_set)); + begin = parse_precision(begin, end, specs.precision, specs.precision_ref, + ctx); + break; + case 'L': + if (arg_type == type::none_type) return begin; + enter_state(state::locale, is_arithmetic_type(arg_type)); + specs.localized = true; + ++begin; + break; + case 'd': + return parse_presentation_type(pres::dec, integral_set); + case 'X': + specs.upper = true; + FMT_FALLTHROUGH; + case 'x': + return parse_presentation_type(pres::hex, integral_set); + case 'o': + return parse_presentation_type(pres::oct, integral_set); + case 'B': + specs.upper = true; + FMT_FALLTHROUGH; + case 'b': + return parse_presentation_type(pres::bin, integral_set); + case 'E': + specs.upper = true; + FMT_FALLTHROUGH; + case 'e': + return parse_presentation_type(pres::exp, float_set); + case 'F': + specs.upper = true; + FMT_FALLTHROUGH; + case 'f': + return parse_presentation_type(pres::fixed, float_set); + case 'G': + specs.upper = true; + FMT_FALLTHROUGH; + case 'g': + return parse_presentation_type(pres::general, float_set); + case 'A': + specs.upper = true; + FMT_FALLTHROUGH; + case 'a': + return parse_presentation_type(pres::hexfloat, float_set); + case 'c': + if (arg_type == type::bool_type) report_error("invalid format specifier"); + return parse_presentation_type(pres::chr, integral_set); + case 's': + return parse_presentation_type(pres::string, + bool_set | string_set | cstring_set); + case 'p': + return parse_presentation_type(pres::pointer, pointer_set | cstring_set); + case '?': + return parse_presentation_type(pres::debug, + char_set | string_set | cstring_set); + case '}': + return begin; + default: { + if (*begin == '}') return begin; + // Parse fill and alignment. + auto fill_end = begin + code_point_length(begin); + if (end - fill_end <= 0) { + report_error("invalid format specifier"); + return begin; + } + if (*begin == '{') { + report_error("invalid fill character '{'"); + return begin; + } + auto align = parse_align(to_ascii(*fill_end)); + enter_state(state::align, align != align::none); + specs.fill = + basic_string_view(begin, to_unsigned(fill_end - begin)); + specs.align = align; + begin = fill_end + 1; + } + } + if (begin == end) return begin; + c = to_ascii(*begin); + } +} + +template +FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + struct id_adapter { + Handler& handler; + int arg_id; + + FMT_CONSTEXPR void on_auto() { arg_id = handler.on_arg_id(); } + FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void on_name(basic_string_view id) { + arg_id = handler.on_arg_id(id); + } + }; + + ++begin; + if (begin == end) return handler.on_error("invalid format string"), end; + if (*begin == '}') { + handler.on_replacement_field(handler.on_arg_id(), begin); + } else if (*begin == '{') { + handler.on_text(begin, begin + 1); + } else { + auto adapter = id_adapter{handler, 0}; + begin = parse_arg_id(begin, end, adapter); + Char c = begin != end ? *begin : Char(); + if (c == '}') { + handler.on_replacement_field(adapter.arg_id, begin); + } else if (c == ':') { + begin = handler.on_format_specs(adapter.arg_id, begin + 1, end); + if (begin == end || *begin != '}') + return handler.on_error("unknown format specifier"), end; + } else { + return handler.on_error("missing '}' in format string"), end; + } + } + return begin + 1; +} + +template +FMT_CONSTEXPR void parse_format_string(basic_string_view format_str, + Handler&& handler) { + auto begin = format_str.data(); + auto end = begin + format_str.size(); + if (end - begin < 32) { + // Use a simple loop instead of memchr for small strings. + const Char* p = begin; + while (p != end) { + auto c = *p++; + if (c == '{') { + handler.on_text(begin, p - 1); + begin = p = parse_replacement_field(p - 1, end, handler); + } else if (c == '}') { + if (p == end || *p != '}') + return handler.on_error("unmatched '}' in format string"); + handler.on_text(begin, p); + begin = ++p; + } + } + handler.on_text(begin, end); + return; + } + struct writer { + FMT_CONSTEXPR void operator()(const Char* from, const Char* to) { + if (from == to) return; + for (;;) { + const Char* p = nullptr; + if (!find(from, to, Char('}'), p)) + return handler_.on_text(from, to); + ++p; + if (p == to || *p != '}') + return handler_.on_error("unmatched '}' in format string"); + handler_.on_text(from, p); + from = p + 1; + } + } + Handler& handler_; + } write = {handler}; + while (begin != end) { + // Doing two passes with memchr (one for '{' and another for '}') is up to + // 2.5x faster than the naive one-pass implementation on big format strings. + const Char* p = begin; + if (*begin != '{' && !find(begin + 1, end, Char('{'), p)) + return write(begin, end); + write(begin, p); + begin = parse_replacement_field(p, end, handler); + } +} + +template ::value> struct strip_named_arg { + using type = T; +}; +template struct strip_named_arg { + using type = remove_cvref_t; +}; + +template +FMT_VISIBILITY("hidden") // Suppress an ld warning on macOS (#3769). +FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) + -> decltype(ctx.begin()) { + using char_type = typename ParseContext::char_type; + using context = buffered_context; + using mapped_type = conditional_t< + mapped_type_constant::value != type::custom_type, + decltype(arg_mapper().map(std::declval())), + typename strip_named_arg::type>; +#if defined(__cpp_if_constexpr) + if constexpr (std::is_default_constructible< + formatter>::value) { + return formatter().parse(ctx); + } else { + type_is_unformattable_for _; + return ctx.begin(); + } +#else + return formatter().parse(ctx); +#endif +} + +// Checks char specs and returns true iff the presentation type is char-like. +FMT_CONSTEXPR inline auto check_char_specs(const format_specs& specs) -> bool { + if (specs.type != presentation_type::none && + specs.type != presentation_type::chr && + specs.type != presentation_type::debug) { + return false; + } + if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) + report_error("invalid format specifier for char"); + return true; +} + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template +constexpr auto get_arg_index_by_name(basic_string_view name) -> int { + if constexpr (is_statically_named_arg()) { + if (name == T::name) return N; + } + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name(name); + (void)name; // Workaround an MSVC bug about "unused" parameter. + return -1; +} +#endif + +template +FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name<0, Args...>(name); +#endif + (void)name; + return -1; +} + +template class format_string_checker { + private: + using parse_context_type = compile_parse_context; + static constexpr int num_args = sizeof...(Args); + + // Format specifier parsing function. + // In the future basic_format_parse_context will replace compile_parse_context + // here and will use is_constant_evaluated and downcasting to access the data + // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. + using parse_func = const Char* (*)(parse_context_type&); + + type types_[num_args > 0 ? static_cast(num_args) : 1]; + parse_context_type context_; + parse_func parse_funcs_[num_args > 0 ? static_cast(num_args) : 1]; + + public: + explicit FMT_CONSTEXPR format_string_checker(basic_string_view fmt) + : types_{mapped_type_constant>::value...}, + context_(fmt, num_args, types_), + parse_funcs_{&parse_format_specs...} {} + + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + + FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + return context_.check_arg_id(id), id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS + auto index = get_arg_index_by_name(id); + if (index < 0) on_error("named argument is not found"); + return index; +#else + (void)id; + on_error("compile-time checks for named arguments require C++20 support"); + return 0; +#endif + } + + FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) { + on_format_specs(id, begin, begin); // Call parse() on empty specs. + } + + FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) + -> const Char* { + context_.advance_to(begin); + // id >= 0 check is a workaround for gcc 10 bug (#2065). + return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; + } + + FMT_NORETURN FMT_CONSTEXPR void on_error(const char* message) { + report_error(message); + } +}; + +// A base class for compile-time strings. +struct compile_string {}; + +template +using is_compile_string = std::is_base_of; + +// Reports a compile-time error if S is not a valid format string. +template ::value)> +FMT_ALWAYS_INLINE void check_format_string(const S&) { +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert(is_compile_string::value, + "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " + "FMT_STRING."); +#endif +} +template ::value)> +void check_format_string(S format_str) { + using char_t = typename S::char_type; + FMT_CONSTEXPR auto s = basic_string_view(format_str); + using checker = format_string_checker...>; + FMT_CONSTEXPR bool error = (parse_format_string(s, checker(s)), true); + ignore_unused(error); +} + +// Use vformat_args and avoid type_identity to keep symbols short and workaround +// a GCC <= 4.8 bug. +template struct vformat_args { + using type = basic_format_args>; +}; +template <> struct vformat_args { + using type = format_args; +}; + +template +void vformat_to(buffer& buf, basic_string_view fmt, + typename vformat_args::type args, locale_ref loc = {}); + +FMT_API void vprint_mojibake(FILE*, string_view, format_args, bool = false); +#ifndef _WIN32 +inline void vprint_mojibake(FILE*, string_view, format_args, bool) {} +#endif + +template struct native_formatter { + private: + dynamic_format_specs specs_; + + public: + using nonlocking = void; + + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* { + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); + auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, TYPE); + if (const_check(TYPE == type::char_type)) check_char_specs(specs_); + return end; + } + + template + FMT_CONSTEXPR void set_debug_format(bool set = true) { + specs_.type = set ? presentation_type::debug : presentation_type::none; + } + + template + FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const + -> decltype(ctx.out()); +}; +} // namespace detail + +FMT_BEGIN_EXPORT + +// A formatter specialization for natively supported types. +template +struct formatter::value != + detail::type::custom_type>> + : detail::native_formatter::value> { +}; + +template struct runtime_format_string { + basic_string_view str; +}; + +/// A compile-time format string. +template class basic_format_string { + private: + basic_string_view str_; + + public: + template < + typename S, + FMT_ENABLE_IF( + std::is_convertible>::value || + (detail::is_compile_string::value && + std::is_constructible, const S&>::value))> + FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_format_string(const S& s) : str_(s) { + static_assert( + detail::count< + (std::is_base_of>::value && + std::is_reference::value)...>() == 0, + "passing views as lvalues is disallowed"); +#if FMT_USE_CONSTEVAL + if constexpr (detail::count_named_args() == + detail::count_statically_named_args()) { + using checker = + detail::format_string_checker...>; + detail::parse_format_string(str_, checker(s)); + } +#else + detail::check_format_string(s); +#endif + } + basic_format_string(runtime_format_string fmt) : str_(fmt.str) {} + + FMT_ALWAYS_INLINE operator basic_string_view() const { return str_; } + auto get() const -> basic_string_view { return str_; } +}; + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 +// Workaround broken conversion on older gcc. +template using format_string = string_view; +inline auto runtime(string_view s) -> string_view { return s; } +#else +template +using format_string = basic_format_string...>; +/** + * Creates a runtime format string. + * + * **Example**: + * + * // Check format string at runtime instead of compile-time. + * fmt::print(fmt::runtime("{:d}"), "I am not a number"); + */ +inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } +#endif + +/// Formats a string and writes the output to `out`. +template , + char>::value)> +auto vformat_to(OutputIt&& out, string_view fmt, format_args args) + -> remove_cvref_t { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, fmt, args, {}); + return detail::get_iterator(buf, out); +} + +/** + * Formats `args` according to specifications in `fmt`, writes the result to + * the output iterator `out` and returns the iterator past the end of the output + * range. `format_to` does not append a terminating null character. + * + * **Example**: + * + * auto out = std::vector(); + * fmt::format_to(std::back_inserter(out), "{}", 42); + */ +template , + char>::value)> +FMT_INLINE auto format_to(OutputIt&& out, format_string fmt, T&&... args) + -> remove_cvref_t { + return vformat_to(FMT_FWD(out), fmt, fmt::make_format_args(args...)); +} + +template struct format_to_n_result { + /// Iterator past the end of the output range. + OutputIt out; + /// Total (not truncated) output size. + size_t size; +}; + +template ::value)> +auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, fmt, args, {}); + return {buf.out(), buf.count()}; +} + +/** + * Formats `args` according to specifications in `fmt`, writes up to `n` + * characters of the result to the output iterator `out` and returns the total + * (not truncated) output size and the iterator past the end of the output + * range. `format_to_n` does not append a terminating null character. + */ +template ::value)> +FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, + T&&... args) -> format_to_n_result { + return vformat_to_n(out, n, fmt, fmt::make_format_args(args...)); +} + +template +struct format_to_result { + /// Iterator pointing to just after the last successful write in the range. + OutputIt out; + /// Specifies if the output was truncated. + bool truncated; + + FMT_CONSTEXPR operator OutputIt&() & noexcept { return out; } + FMT_CONSTEXPR operator const OutputIt&() const& noexcept { return out; } + FMT_CONSTEXPR operator OutputIt&&() && noexcept { + return static_cast(out); + } +}; + +template +auto vformat_to(char (&out)[N], string_view fmt, format_args args) + -> format_to_result { + auto result = vformat_to_n(out, N, fmt, args); + return {result.out, result.size > N}; +} + +template +FMT_INLINE auto format_to(char (&out)[N], format_string fmt, T&&... args) + -> format_to_result { + auto result = fmt::format_to_n(out, N, fmt, static_cast(args)...); + return {result.out, result.size > N}; +} + +/// Returns the number of chars in the output of `format(fmt, args...)`. +template +FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, fmt, fmt::make_format_args(args...), {}); + return buf.count(); +} + +FMT_API void vprint(string_view fmt, format_args args); +FMT_API void vprint(FILE* f, string_view fmt, format_args args); +FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); +FMT_API void vprintln(FILE* f, string_view fmt, format_args args); + +/** + * Formats `args` according to specifications in `fmt` and writes the output + * to `stdout`. + * + * **Example**: + * + * fmt::print("The answer is {}.", 42); + */ +template +FMT_INLINE void print(format_string fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + if (!detail::use_utf8()) return detail::vprint_mojibake(stdout, fmt, vargs); + return detail::is_locking() ? vprint_buffered(stdout, fmt, vargs) + : vprint(fmt, vargs); +} + +/** + * Formats `args` according to specifications in `fmt` and writes the + * output to the file `f`. + * + * **Example**: + * + * fmt::print(stderr, "Don't {}!", "panic"); + */ +template +FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + if (!detail::use_utf8()) return detail::vprint_mojibake(f, fmt, vargs); + return detail::is_locking() ? vprint_buffered(f, fmt, vargs) + : vprint(f, fmt, vargs); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to the file `f` followed by a newline. +template +FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + return detail::use_utf8() ? vprintln(f, fmt, vargs) + : detail::vprint_mojibake(f, fmt, vargs, true); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to `stdout` followed by a newline. +template +FMT_INLINE void println(format_string fmt, T&&... args) { + return fmt::println(stdout, fmt, static_cast(args)...); +} + +FMT_END_EXPORT +FMT_GCC_PRAGMA("GCC pop_options") +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# include "format.h" +#endif +#endif // FMT_BASE_H_ diff --git a/src/3rdparty/fmt/chrono.h b/src/3rdparty/fmt/chrono.h index 61602f68..77d04032 100644 --- a/src/3rdparty/fmt/chrono.h +++ b/src/3rdparty/fmt/chrono.h @@ -8,16 +8,54 @@ #ifndef FMT_CHRONO_H_ #define FMT_CHRONO_H_ -#include -#include -#include -#include -#include +#ifndef FMT_IMPORT_STD +# include +# include +# include // std::isfinite +# include // std::memcpy +# include +# include +# include +# include +# include +#endif #include "format.h" FMT_BEGIN_NAMESPACE +// Check if std::chrono::local_t is available. +#ifndef FMT_USE_LOCAL_TIME +# ifdef __cpp_lib_chrono +# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) +# else +# define FMT_USE_LOCAL_TIME 0 +# endif +#endif + +// Check if std::chrono::utc_timestamp is available. +#ifndef FMT_USE_UTC_TIME +# ifdef __cpp_lib_chrono +# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) +# else +# define FMT_USE_UTC_TIME 0 +# endif +#endif + +// Enable tzset. +#ifndef FMT_USE_TZSET +// UWP doesn't provide _tzset. +# if FMT_HAS_INCLUDE("winapifamily.h") +# include +# endif +# if defined(_WIN32) && (!defined(WINAPI_FAMILY) || \ + (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +# define FMT_USE_TZSET 1 +# else +# define FMT_USE_TZSET 0 +# endif +#endif + // Enable safe chrono durations, unless explicitly disabled. #ifndef FMT_SAFE_DURATION_CAST # define FMT_SAFE_DURATION_CAST 1 @@ -36,7 +74,8 @@ template ::value && std::numeric_limits::is_signed == std::numeric_limits::is_signed)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; using F = std::numeric_limits; using T = std::numeric_limits; @@ -44,7 +83,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { static_assert(T::is_integer, "To must be integral"); // A and B are both signed, or both unsigned. - if (F::digits <= T::digits) { + if (detail::const_check(F::digits <= T::digits)) { // From fits in To without any problem. } else { // From does not always fit in To, resort to a dynamic check. @@ -57,15 +96,14 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { return static_cast(from); } -/** - * converts From to To, without loss. If the dynamic value of from - * can't be converted to To without loss, ec is set. - */ +/// Converts From to To, without loss. If the dynamic value of from +/// can't be converted to To without loss, ec is set. template ::value && std::numeric_limits::is_signed != std::numeric_limits::is_signed)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; using F = std::numeric_limits; using T = std::numeric_limits; @@ -79,14 +117,15 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { return {}; } // From is positive. Can it always fit in To? - if (F::digits > T::digits && + if (detail::const_check(F::digits > T::digits) && from > static_cast(detail::max_value())) { ec = 1; return {}; } } - if (!F::is_signed && T::is_signed && F::digits >= T::digits && + if (detail::const_check(!F::is_signed && T::is_signed && + F::digits >= T::digits) && from > static_cast(detail::max_value())) { ec = 1; return {}; @@ -96,7 +135,8 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { template ::value)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; return from; } // function @@ -117,7 +157,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { // clang-format on template ::value)> -FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { ec = 0; using T = std::numeric_limits; static_assert(std::is_floating_point::value, "From must be floating"); @@ -139,20 +179,18 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { template ::value)> -FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { ec = 0; static_assert(std::is_floating_point::value, "From must be floating"); return from; } -/** - * safe duration cast between integral durations - */ +/// Safe duration cast between integral durations template ::value), FMT_ENABLE_IF(std::is_integral::value)> -To safe_duration_cast(std::chrono::duration from, - int& ec) { +auto safe_duration_cast(std::chrono::duration from, + int& ec) -> To { using From = std::chrono::duration; ec = 0; // the basic idea is that we need to convert from count() in the from type @@ -184,7 +222,8 @@ To safe_duration_cast(std::chrono::duration from, } const auto min1 = (std::numeric_limits::min)() / Factor::num; - if (count < min1) { + if (detail::const_check(!std::is_unsigned::value) && + count < min1) { ec = 1; return {}; } @@ -196,14 +235,12 @@ To safe_duration_cast(std::chrono::duration from, return ec ? To() : To(tocount); } -/** - * safe duration_cast between floating point durations - */ +/// Safe duration_cast between floating point durations template ::value), FMT_ENABLE_IF(std::is_floating_point::value)> -To safe_duration_cast(std::chrono::duration from, - int& ec) { +auto safe_duration_cast(std::chrono::duration from, + int& ec) -> To { using From = std::chrono::duration; ec = 0; if (std::isnan(from.count())) { @@ -243,7 +280,7 @@ To safe_duration_cast(std::chrono::duration from, } // multiply with Factor::num without overflow or underflow - if (Factor::num != 1) { + if (detail::const_check(Factor::num != 1)) { constexpr auto max1 = detail::max_value() / static_cast(Factor::num); if (count > max1) { @@ -260,7 +297,7 @@ To safe_duration_cast(std::chrono::duration from, } // this can't go wrong, right? den>0 is checked earlier. - if (Factor::den != 1) { + if (detail::const_check(Factor::den != 1)) { using common_t = typename std::common_type::type; count /= static_cast(Factor::den); } @@ -283,121 +320,225 @@ To safe_duration_cast(std::chrono::duration from, namespace detail { template struct null {}; -inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } -inline null<> localtime_s(...) { return null<>(); } -inline null<> gmtime_r(...) { return null<>(); } -inline null<> gmtime_s(...) { return null<>(); } - -inline auto do_write(const std::tm& time, const std::locale& loc, char format, - char modifier) -> std::string { - auto&& os = std::ostringstream(); - os.imbue(loc); - using iterator = std::ostreambuf_iterator; - const auto& facet = std::use_facet>(loc); - auto end = facet.put(os, os, ' ', &time, format, modifier); - if (end.failed()) FMT_THROW(format_error("failed to format time")); - auto str = os.str(); - if (!detail::is_utf8() || loc == std::locale::classic()) return str; - // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and - // gcc-4. -#if FMT_MSC_VER != 0 || \ - (defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI)) - // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 - // and newer. - using code_unit = wchar_t; -#else - using code_unit = char32_t; -#endif +inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); } +inline auto localtime_s(...) -> null<> { return null<>(); } +inline auto gmtime_r(...) -> null<> { return null<>(); } +inline auto gmtime_s(...) -> null<> { return null<>(); } + +// It is defined here and not in ostream.h because the latter has expensive +// includes. +template class formatbuf : public Streambuf { + private: + using char_type = typename Streambuf::char_type; + using streamsize = decltype(std::declval().sputn(nullptr, 0)); + using int_type = typename Streambuf::int_type; + using traits_type = typename Streambuf::traits_type; + + buffer& buffer_; + + public: + explicit formatbuf(buffer& buf) : buffer_(buf) {} + + protected: + // The put area is always empty. This makes the implementation simpler and has + // the advantage that the streambuf and the buffer are always in sync and + // sputc never writes into uninitialized memory. A disadvantage is that each + // call to sputc always results in a (virtual) call to overflow. There is no + // disadvantage here for sputn since this always results in a call to xsputn. + + auto overflow(int_type ch) -> int_type override { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + auto xsputn(const char_type* s, streamsize count) -> streamsize override { + buffer_.append(s, s + count); + return count; + } +}; + +inline auto get_classic_locale() -> const std::locale& { + static const auto& locale = std::locale::classic(); + return locale; +} + +template struct codecvt_result { + static constexpr const size_t max_size = 32; + CodeUnit buf[max_size]; + CodeUnit* end; +}; - using codecvt = std::codecvt; +template +void write_codecvt(codecvt_result& out, string_view in_buf, + const std::locale& loc) { #if FMT_CLANG_VERSION # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated" - auto& f = std::use_facet(loc); + auto& f = std::use_facet>(loc); # pragma clang diagnostic pop #else - auto& f = std::use_facet(loc); + auto& f = std::use_facet>(loc); #endif - auto mb = std::mbstate_t(); const char* from_next = nullptr; - code_unit* to_next = nullptr; - constexpr size_t buf_size = 32; - code_unit buf[buf_size] = {}; - auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf, - buf + buf_size, to_next); + auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next, + std::begin(out.buf), std::end(out.buf), out.end); if (result != std::codecvt_base::ok) FMT_THROW(format_error("failed to format time")); - str.clear(); - for (code_unit* p = buf; p != to_next; ++p) { - uint32_t c = static_cast(*p); - if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) { - // surrogate pair - ++p; - if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { - FMT_THROW(format_error("failed to format time")); - } - c = (c << 10) + static_cast(*p) - 0x35fdc00; - } - if (c < 0x80) { - str.push_back(static_cast(c)); - } else if (c < 0x800) { - str.push_back(static_cast(0xc0 | (c >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); - } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { - str.push_back(static_cast(0xe0 | (c >> 12))); - str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); - } else if (c >= 0x10000 && c <= 0x10ffff) { - str.push_back(static_cast(0xf0 | (c >> 18))); - str.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); - str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); - } else { +} + +template +auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) + -> OutputIt { + if (detail::use_utf8() && loc != get_classic_locale()) { + // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and + // gcc-4. +#if FMT_MSC_VERSION != 0 || \ + (defined(__GLIBCXX__) && \ + (!defined(_GLIBCXX_USE_DUAL_ABI) || _GLIBCXX_USE_DUAL_ABI == 0)) + // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 + // and newer. + using code_unit = wchar_t; +#else + using code_unit = char32_t; +#endif + + using unit_t = codecvt_result; + unit_t unit; + write_codecvt(unit, in, loc); + // In UTF-8 is used one to four one-byte code units. + auto u = + to_utf8>(); + if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)})) FMT_THROW(format_error("failed to format time")); - } + return copy(u.c_str(), u.c_str() + u.size(), out); } - return str; + return copy(in.data(), in.data() + in.size(), out); } -template +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + codecvt_result unit; + write_codecvt(unit, sv, loc); + return copy(unit.buf, unit.end, out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + return write_encoded_tm_str(out, sv, loc); +} + +template +inline void do_write(buffer& buf, const std::tm& time, + const std::locale& loc, char format, char modifier) { + auto&& format_buf = formatbuf>(buf); + auto&& os = std::basic_ostream(&format_buf); + os.imbue(loc); + const auto& facet = std::use_facet>(loc); + auto end = facet.put(os, os, Char(' '), &time, format, modifier); + if (end.failed()) FMT_THROW(format_error("failed to format time")); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = get_buffer(out); + do_write(buf, time, loc, format, modifier); + return get_iterator(buf, out); +} + +template ::value)> auto write(OutputIt out, const std::tm& time, const std::locale& loc, char format, char modifier = 0) -> OutputIt { - auto str = do_write(time, loc, format, modifier); - return std::copy(str.begin(), str.end(), out); + auto&& buf = basic_memory_buffer(); + do_write(buf, time, loc, format, modifier); + return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); +} + +template +struct is_same_arithmetic_type + : public std::integral_constant::value && + std::is_integral::value) || + (std::is_floating_point::value && + std::is_floating_point::value)> { +}; + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(is_same_arithmetic_type::value)> +auto fmt_duration_cast(std::chrono::duration from) -> To { +#if FMT_SAFE_DURATION_CAST + // Throwing version of safe_duration_cast is only available for + // integer to integer or float to float casts. + int ec; + To to = safe_duration_cast::safe_duration_cast(from, ec); + if (ec) FMT_THROW(format_error("cannot format duration")); + return to; +#else + // Standard duration cast, may overflow. + return std::chrono::duration_cast(from); +#endif +} + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(!is_same_arithmetic_type::value)> +auto fmt_duration_cast(std::chrono::duration from) -> To { + // Mixed integer <-> float cast is not supported by safe_duration_cast. + return std::chrono::duration_cast(from); +} + +template +auto to_time_t( + std::chrono::time_point time_point) + -> std::time_t { + // Cannot use std::chrono::system_clock::to_time_t since this would first + // require a cast to std::chrono::system_clock::time_point, which could + // overflow. + return fmt_duration_cast>( + time_point.time_since_epoch()) + .count(); } } // namespace detail -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT /** - Converts given time since epoch as ``std::time_t`` value into calendar time, - expressed in local time. Unlike ``std::localtime``, this function is - thread-safe on most platforms. + * Converts given time since epoch as `std::time_t` value into calendar time, + * expressed in local time. Unlike `std::localtime`, this function is + * thread-safe on most platforms. */ -inline std::tm localtime(std::time_t time) { +inline auto localtime(std::time_t time) -> std::tm { struct dispatcher { std::time_t time_; std::tm tm_; dispatcher(std::time_t t) : time_(t) {} - bool run() { + auto run() -> bool { using namespace fmt::detail; return handle(localtime_r(&time_, &tm_)); } - bool handle(std::tm* tm) { return tm != nullptr; } + auto handle(std::tm* tm) -> bool { return tm != nullptr; } - bool handle(detail::null<>) { + auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(localtime_s(&tm_, &time_)); } - bool fallback(int res) { return res == 0; } + auto fallback(int res) -> bool { return res == 0; } -#if !FMT_MSC_VER - bool fallback(detail::null<>) { +#if !FMT_MSC_VERSION + auto fallback(detail::null<>) -> bool { using namespace fmt::detail; std::tm* tm = std::localtime(&time_); if (tm) tm_ = *tm; @@ -411,76 +552,62 @@ inline std::tm localtime(std::time_t time) { return lt.tm_; } -inline std::tm localtime( - std::chrono::time_point time_point) { - return localtime(std::chrono::system_clock::to_time_t(time_point)); +#if FMT_USE_LOCAL_TIME +template +inline auto localtime(std::chrono::local_time time) -> std::tm { + return localtime( + detail::to_time_t(std::chrono::current_zone()->to_sys(time))); } +#endif /** - Converts given time since epoch as ``std::time_t`` value into calendar time, - expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this - function is thread-safe on most platforms. + * Converts given time since epoch as `std::time_t` value into calendar time, + * expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this + * function is thread-safe on most platforms. */ -inline std::tm gmtime(std::time_t time) { +inline auto gmtime(std::time_t time) -> std::tm { struct dispatcher { std::time_t time_; std::tm tm_; dispatcher(std::time_t t) : time_(t) {} - bool run() { + auto run() -> bool { using namespace fmt::detail; return handle(gmtime_r(&time_, &tm_)); } - bool handle(std::tm* tm) { return tm != nullptr; } + auto handle(std::tm* tm) -> bool { return tm != nullptr; } - bool handle(detail::null<>) { + auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(gmtime_s(&tm_, &time_)); } - bool fallback(int res) { return res == 0; } + auto fallback(int res) -> bool { return res == 0; } -#if !FMT_MSC_VER - bool fallback(detail::null<>) { +#if !FMT_MSC_VERSION + auto fallback(detail::null<>) -> bool { std::tm* tm = std::gmtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; } #endif }; - dispatcher gt(time); + auto gt = dispatcher(time); // Too big time values may be unsupported. if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); return gt.tm_; } -inline std::tm gmtime( - std::chrono::time_point time_point) { - return gmtime(std::chrono::system_clock::to_time_t(time_point)); -} - -FMT_BEGIN_DETAIL_NAMESPACE - -inline size_t strftime(char* str, size_t count, const char* format, - const std::tm* time) { - // Assign to a pointer to suppress GCCs -Wformat-nonliteral - // First assign the nullptr to suppress -Wsuggest-attribute=format - std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) = - nullptr; - strftime = std::strftime; - return strftime(str, count, format, time); +template +inline auto gmtime( + std::chrono::time_point time_point) + -> std::tm { + return gmtime(detail::to_time_t(time_point)); } -inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format, - const std::tm* time) { - // See above - std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*, - const std::tm*) = nullptr; - wcsftime = std::wcsftime; - return wcsftime(str, count, format, time); -} +namespace detail { // Writes two-digit numbers a, b and c separated by sep to buf. // The method by Pavel Novikov based on @@ -505,120 +632,19 @@ inline void write_digit2_separated(char* buf, unsigned a, unsigned b, auto usep = static_cast(sep); // Add ASCII '0' to each digit byte and insert separators. digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); - memcpy(buf, &digits, 8); -} - -FMT_END_DETAIL_NAMESPACE - -template -struct formatter, - Char> : formatter { - FMT_CONSTEXPR formatter() { - this->specs = {default_specs, sizeof(default_specs) / sizeof(Char)}; - } - - template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(); - if (it != ctx.end() && *it == ':') ++it; - auto end = it; - while (end != ctx.end() && *end != '}') ++end; - if (end != it) this->specs = {it, detail::to_unsigned(end - it)}; - return end; - } - - template - auto format(std::chrono::time_point val, - FormatContext& ctx) -> decltype(ctx.out()) { - std::tm time = localtime(val); - return formatter::format(time, ctx); - } - - static constexpr Char default_specs[] = {'%', 'Y', '-', '%', 'm', '-', - '%', 'd', ' ', '%', 'H', ':', - '%', 'M', ':', '%', 'S'}; -}; -template -constexpr Char - formatter, - Char>::default_specs[]; - -template struct formatter { - private: - enum class spec { - unknown, - year_month_day, - hh_mm_ss, - }; - spec spec_ = spec::unknown; - - public: - basic_string_view specs; - - template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(); - if (it != ctx.end() && *it == ':') ++it; - auto end = it; - while (end != ctx.end() && *end != '}') ++end; - auto size = detail::to_unsigned(end - it); - specs = {it, size}; - // basic_string_view<>::compare isn't constexpr before C++17 - if (specs.size() == 2 && specs[0] == Char('%')) { - if (specs[1] == Char('F')) - spec_ = spec::year_month_day; - else if (specs[1] == Char('T')) - spec_ = spec::hh_mm_ss; - } - return end; - } - - template - auto format(const std::tm& tm, FormatContext& ctx) const - -> decltype(ctx.out()) { - auto year = 1900 + tm.tm_year; - if (spec_ == spec::year_month_day && year >= 0 && year < 10000) { - char buf[10]; - detail::copy2(buf, detail::digits2(detail::to_unsigned(year / 100))); - detail::write_digit2_separated(buf + 2, year % 100, - detail::to_unsigned(tm.tm_mon + 1), - detail::to_unsigned(tm.tm_mday), '-'); - return std::copy_n(buf, sizeof(buf), ctx.out()); - } else if (spec_ == spec::hh_mm_ss) { - char buf[8]; - detail::write_digit2_separated(buf, detail::to_unsigned(tm.tm_hour), - detail::to_unsigned(tm.tm_min), - detail::to_unsigned(tm.tm_sec), ':'); - return std::copy_n(buf, sizeof(buf), ctx.out()); - } - basic_memory_buffer tm_format; - tm_format.append(specs.begin(), specs.end()); - // By appending an extra space we can distinguish an empty result that - // indicates insufficient buffer size from a guaranteed non-empty result - // https://github.com/fmtlib/fmt/issues/2238 - tm_format.push_back(' '); - tm_format.push_back('\0'); - basic_memory_buffer buf; - size_t start = buf.size(); - for (;;) { - size_t size = buf.capacity() - start; - size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm); - if (count != 0) { - buf.resize(start + count); - break; - } - const size_t MIN_GROWTH = 10; - buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); - } - // Remove the extra space. - return std::copy(buf.begin(), buf.end() - 1, ctx.out()); + constexpr const size_t len = 8; + if (const_check(is_big_endian())) { + char tmp[len]; + std::memcpy(tmp, &digits, len); + std::reverse_copy(tmp, tmp + len, buf); + } else { + std::memcpy(buf, &digits, len); } -}; - -FMT_BEGIN_DETAIL_NAMESPACE +} -template FMT_CONSTEXPR inline const char* get_units() { +template +FMT_CONSTEXPR inline auto get_units() -> const char* { if (std::is_same::value) return "as"; if (std::is_same::value) return "fs"; if (std::is_same::value) return "ps"; @@ -636,8 +662,9 @@ template FMT_CONSTEXPR inline const char* get_units() { if (std::is_same::value) return "Ts"; if (std::is_same::value) return "Ps"; if (std::is_same::value) return "Es"; - if (std::is_same>::value) return "m"; + if (std::is_same>::value) return "min"; if (std::is_same>::value) return "h"; + if (std::is_same>::value) return "d"; return nullptr; } @@ -647,13 +674,37 @@ enum class numeric_system { alternative }; +// Glibc extensions for formatting numeric values. +enum class pad_type { + // Pad a numeric result string with zeros (the default). + zero, + // Do not pad a numeric result string. + none, + // Pad a numeric result string with spaces. + space, +}; + +template +auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { + if (pad == pad_type::none) return out; + return detail::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); +} + +template +auto write_padding(OutputIt out, pad_type pad) -> OutputIt { + if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0'; + return out; +} + // Parses a put_time-like format string and invokes handler actions. template -FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, - const Char* end, - Handler&& handler) { +FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + if (begin == end || *begin == '}') return begin; + if (*begin != '%') FMT_THROW(format_error("invalid format")); auto ptr = begin; while (ptr != end) { + pad_type pad = pad_type::zero; auto c = *ptr; if (c == '}') break; if (c != '%') { @@ -663,6 +714,18 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (begin != ptr) handler.on_text(begin, ptr); ++ptr; // consume '%' if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr; + switch (c) { + case '_': + pad = pad_type::space; + ++ptr; + break; + case '-': + pad = pad_type::none; + ++ptr; + break; + } + if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case '%': @@ -678,6 +741,22 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_text(tab, tab + 1); break; } + // Year: + case 'Y': + handler.on_year(numeric_system::standard); + break; + case 'y': + handler.on_short_year(numeric_system::standard); + break; + case 'C': + handler.on_century(numeric_system::standard); + break; + case 'G': + handler.on_iso_week_based_year(); + break; + case 'g': + handler.on_iso_week_based_short_year(); + break; // Day of the week: case 'a': handler.on_abbr_weekday(); @@ -693,23 +772,46 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, break; // Month: case 'b': + case 'h': handler.on_abbr_month(); break; case 'B': handler.on_full_month(); break; + case 'm': + handler.on_dec_month(numeric_system::standard); + break; + // Day of the year/month: + case 'U': + handler.on_dec0_week_of_year(numeric_system::standard, pad); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::standard, pad); + break; + case 'V': + handler.on_iso_week_of_year(numeric_system::standard, pad); + break; + case 'j': + handler.on_day_of_year(); + break; + case 'd': + handler.on_day_of_month(numeric_system::standard, pad); + break; + case 'e': + handler.on_day_of_month(numeric_system::standard, pad_type::space); + break; // Hour, minute, second: case 'H': - handler.on_24_hour(numeric_system::standard); + handler.on_24_hour(numeric_system::standard, pad); break; case 'I': - handler.on_12_hour(numeric_system::standard); + handler.on_12_hour(numeric_system::standard, pad); break; case 'M': - handler.on_minute(numeric_system::standard); + handler.on_minute(numeric_system::standard, pad); break; case 'S': - handler.on_second(numeric_system::standard); + handler.on_second(numeric_system::standard, pad); break; // Other: case 'c': @@ -746,7 +848,7 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_duration_unit(); break; case 'z': - handler.on_utc_offset(); + handler.on_utc_offset(numeric_system::standard); break; case 'Z': handler.on_tz_name(); @@ -756,6 +858,15 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { + case 'Y': + handler.on_year(numeric_system::alternative); + break; + case 'y': + handler.on_offset_year(); + break; + case 'C': + handler.on_century(numeric_system::alternative); + break; case 'c': handler.on_datetime(numeric_system::alternative); break; @@ -765,6 +876,9 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, case 'X': handler.on_loc_time(numeric_system::alternative); break; + case 'z': + handler.on_utc_offset(numeric_system::alternative); + break; default: FMT_THROW(format_error("invalid format")); } @@ -774,6 +888,27 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { + case 'y': + handler.on_short_year(numeric_system::alternative); + break; + case 'm': + handler.on_dec_month(numeric_system::alternative); + break; + case 'U': + handler.on_dec0_week_of_year(numeric_system::alternative, pad); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::alternative, pad); + break; + case 'V': + handler.on_iso_week_of_year(numeric_system::alternative, pad); + break; + case 'd': + handler.on_day_of_month(numeric_system::alternative, pad); + break; + case 'e': + handler.on_day_of_month(numeric_system::alternative, pad_type::space); + break; case 'w': handler.on_dec0_weekday(numeric_system::alternative); break; @@ -781,16 +916,19 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_dec1_weekday(numeric_system::alternative); break; case 'H': - handler.on_24_hour(numeric_system::alternative); + handler.on_24_hour(numeric_system::alternative, pad); break; case 'I': - handler.on_12_hour(numeric_system::alternative); + handler.on_12_hour(numeric_system::alternative, pad); break; case 'M': - handler.on_minute(numeric_system::alternative); + handler.on_minute(numeric_system::alternative, pad); break; case 'S': - handler.on_second(numeric_system::alternative); + handler.on_second(numeric_system::alternative, pad); + break; + case 'z': + handler.on_utc_offset(numeric_system::alternative); break; default: FMT_THROW(format_error("invalid format")); @@ -809,12 +947,32 @@ template struct null_chrono_spec_handler { FMT_CONSTEXPR void unsupported() { static_cast(this)->unsupported(); } + FMT_CONSTEXPR void on_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_offset_year() { unsupported(); } + FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); } FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); } FMT_CONSTEXPR void on_full_weekday() { unsupported(); } FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_abbr_month() { unsupported(); } FMT_CONSTEXPR void on_full_month() { unsupported(); } + FMT_CONSTEXPR void on_dec_month(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_day_of_year() { unsupported(); } + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) { + unsupported(); + } FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); } @@ -830,64 +988,702 @@ template struct null_chrono_spec_handler { FMT_CONSTEXPR void on_am_pm() { unsupported(); } FMT_CONSTEXPR void on_duration_value() { unsupported(); } FMT_CONSTEXPR void on_duration_unit() { unsupported(); } - FMT_CONSTEXPR void on_utc_offset() { unsupported(); } + FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_tz_name() { unsupported(); } }; -struct chrono_format_checker : null_chrono_spec_handler { - FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } +struct tm_format_checker : null_chrono_spec_handler { + FMT_NORETURN void unsupported() { FMT_THROW(format_error("no format")); } template FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - FMT_CONSTEXPR void on_24_hour(numeric_system) {} - FMT_CONSTEXPR void on_12_hour(numeric_system) {} - FMT_CONSTEXPR void on_minute(numeric_system) {} - FMT_CONSTEXPR void on_second(numeric_system) {} + FMT_CONSTEXPR void on_year(numeric_system) {} + FMT_CONSTEXPR void on_short_year(numeric_system) {} + FMT_CONSTEXPR void on_offset_year() {} + FMT_CONSTEXPR void on_century(numeric_system) {} + FMT_CONSTEXPR void on_iso_week_based_year() {} + FMT_CONSTEXPR void on_iso_week_based_short_year() {} + FMT_CONSTEXPR void on_abbr_weekday() {} + FMT_CONSTEXPR void on_full_weekday() {} + FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {} + FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {} + FMT_CONSTEXPR void on_abbr_month() {} + FMT_CONSTEXPR void on_full_month() {} + FMT_CONSTEXPR void on_dec_month(numeric_system) {} + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_day_of_year() {} + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_datetime(numeric_system) {} + FMT_CONSTEXPR void on_loc_date(numeric_system) {} + FMT_CONSTEXPR void on_loc_time(numeric_system) {} + FMT_CONSTEXPR void on_us_date() {} + FMT_CONSTEXPR void on_iso_date() {} FMT_CONSTEXPR void on_12_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} - FMT_CONSTEXPR void on_duration_value() {} - FMT_CONSTEXPR void on_duration_unit() {} + FMT_CONSTEXPR void on_utc_offset(numeric_system) {} + FMT_CONSTEXPR void on_tz_name() {} }; -template ::value)> -inline bool isnan(T) { - return false; +inline auto tm_wday_full_name(int wday) -> const char* { + static constexpr const char* full_name_list[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"}; + return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; } -template ::value)> -inline bool isnan(T value) { - return std::isnan(value); +inline auto tm_wday_short_name(int wday) -> const char* { + static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; + return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; } -template ::value)> -inline bool isfinite(T) { - return true; +inline auto tm_mon_full_name(int mon) -> const char* { + static constexpr const char* full_name_list[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; +} +inline auto tm_mon_short_name(int mon) -> const char* { + static constexpr const char* short_name_list[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???"; } -// Converts value to int and checks that it's in the range [0, upper). -template ::value)> -inline int to_nonnegative_int(T value, int upper) { - FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper), - "invalid value"); - (void)upper; - return static_cast(value); +template +struct has_member_data_tm_gmtoff : std::false_type {}; +template +struct has_member_data_tm_gmtoff> + : std::true_type {}; + +template +struct has_member_data_tm_zone : std::false_type {}; +template +struct has_member_data_tm_zone> + : std::true_type {}; + +#if FMT_USE_TZSET +inline void tzset_once() { + static bool init = []() -> bool { + _tzset(); + return true; + }(); + ignore_unused(init); +} +#endif + +// Converts value to Int and checks that it's in the range [0, upper). +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + if (!std::is_unsigned::value && + (value < 0 || to_unsigned(value) > to_unsigned(upper))) { + FMT_THROW(fmt::format_error("chrono value is out of range")); + } + return static_cast(value); +} +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + auto int_value = static_cast(value); + if (int_value < 0 || value > static_cast(upper)) + FMT_THROW(format_error("invalid value")); + return int_value; +} + +constexpr auto pow10(std::uint32_t n) -> long long { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +// Counts the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +template () / 10)> +struct count_fractional_digits { + static constexpr int value = + Num % Den == 0 ? N : count_fractional_digits::value; +}; + +// Base case that doesn't instantiate any more templates +// in order to avoid overflow. +template +struct count_fractional_digits { + static constexpr int value = (Num % Den == 0) ? N : 6; +}; + +// Format subseconds which are given as an integer type with an appropriate +// number of digits. +template +void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { + constexpr auto num_fractional_digits = + count_fractional_digits::value; + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, detail::pow10(num_fractional_digits)>>; + + const auto fractional = d - fmt_duration_cast(d); + const auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? fractional.count() + : fmt_duration_cast(fractional).count(); + auto n = static_cast>(subseconds); + const int num_digits = detail::count_digits(n); + + int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits); + if (precision < 0) { + FMT_ASSERT(!std::is_floating_point::value, ""); + if (std::ratio_less::value) { + *out++ = '.'; + out = detail::fill_n(out, leading_zeroes, '0'); + out = format_decimal(out, n, num_digits).end; + } + } else if (precision > 0) { + *out++ = '.'; + leading_zeroes = (std::min)(leading_zeroes, precision); + int remaining = precision - leading_zeroes; + out = detail::fill_n(out, leading_zeroes, '0'); + if (remaining < num_digits) { + int num_truncated_digits = num_digits - remaining; + n /= to_unsigned(detail::pow10(to_unsigned(num_truncated_digits))); + if (n) { + out = format_decimal(out, n, remaining).end; + } + return; + } + if (n) { + out = format_decimal(out, n, num_digits).end; + remaining -= num_digits; + } + out = detail::fill_n(out, remaining, '0'); + } } -template ::value)> -inline int to_nonnegative_int(T value, int upper) { - FMT_ASSERT( - std::isnan(value) || (value >= 0 && value <= static_cast(upper)), - "invalid value"); - (void)upper; - return static_cast(value); + +// Format subseconds which are given as a floating point type with an +// appropriate number of digits. We cannot pass the Duration here, as we +// explicitly need to pass the Rep value in the chrono_formatter. +template +void write_floating_seconds(memory_buffer& buf, Duration duration, + int num_fractional_digits = -1) { + using rep = typename Duration::rep; + FMT_ASSERT(std::is_floating_point::value, ""); + + auto val = duration.count(); + + if (num_fractional_digits < 0) { + // For `std::round` with fallback to `round`: + // On some toolchains `std::round` is not available (e.g. GCC 6). + using namespace std; + num_fractional_digits = + count_fractional_digits::value; + if (num_fractional_digits < 6 && static_cast(round(val)) != val) + num_fractional_digits = 6; + } + + fmt::format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), + std::fmod(val * static_cast(Duration::period::num) / + static_cast(Duration::period::den), + static_cast(60)), + num_fractional_digits); +} + +template +class tm_writer { + private: + static constexpr int days_per_week = 7; + + const std::locale& loc_; + const bool is_classic_; + OutputIt out_; + const Duration* subsecs_; + const std::tm& tm_; + + auto tm_sec() const noexcept -> int { + FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, ""); + return tm_.tm_sec; + } + auto tm_min() const noexcept -> int { + FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, ""); + return tm_.tm_min; + } + auto tm_hour() const noexcept -> int { + FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, ""); + return tm_.tm_hour; + } + auto tm_mday() const noexcept -> int { + FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, ""); + return tm_.tm_mday; + } + auto tm_mon() const noexcept -> int { + FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, ""); + return tm_.tm_mon; + } + auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; } + auto tm_wday() const noexcept -> int { + FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, ""); + return tm_.tm_wday; + } + auto tm_yday() const noexcept -> int { + FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, ""); + return tm_.tm_yday; + } + + auto tm_hour12() const noexcept -> int { + const auto h = tm_hour(); + const auto z = h < 12 ? h : h - 12; + return z == 0 ? 12 : z; + } + + // POSIX and the C Standard are unclear or inconsistent about what %C and %y + // do if the year is negative or exceeds 9999. Use the convention that %C + // concatenated with %y yields the same output as %Y, and that %Y contains at + // least 4 characters, with more only if necessary. + auto split_year_lower(long long year) const noexcept -> int { + auto l = year % 100; + if (l < 0) l = -l; // l in [0, 99] + return static_cast(l); + } + + // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. + auto iso_year_weeks(long long curr_year) const noexcept -> int { + const auto prev_year = curr_year - 1; + const auto curr_p = + (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % + days_per_week; + const auto prev_p = + (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % + days_per_week; + return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); + } + auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int { + return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) / + days_per_week; + } + auto tm_iso_week_year() const noexcept -> long long { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return year - 1; + if (w > iso_year_weeks(year)) return year + 1; + return year; + } + auto tm_iso_week_of_year() const noexcept -> int { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return iso_year_weeks(year - 1); + if (w > iso_year_weeks(year)) return 1; + return w; + } + + void write1(int value) { + *out_++ = static_cast('0' + to_unsigned(value) % 10); + } + void write2(int value) { + const char* d = digits2(to_unsigned(value) % 100); + *out_++ = *d++; + *out_++ = *d; + } + void write2(int value, pad_type pad) { + unsigned int v = to_unsigned(value) % 100; + if (v >= 10) { + const char* d = digits2(v); + *out_++ = *d++; + *out_++ = *d; + } else { + out_ = detail::write_padding(out_, pad); + *out_++ = static_cast('0' + v); + } + } + + void write_year_extended(long long year) { + // At least 4 characters. + int width = 4; + if (year < 0) { + *out_++ = '-'; + year = 0 - year; + --width; + } + uint32_or_64_or_128_t n = to_unsigned(year); + const int num_digits = count_digits(n); + if (width > num_digits) + out_ = detail::fill_n(out_, width - num_digits, '0'); + out_ = format_decimal(out_, n, num_digits).end; + } + void write_year(long long year) { + if (year >= 0 && year < 10000) { + write2(static_cast(year / 100)); + write2(static_cast(year % 100)); + } else { + write_year_extended(year); + } + } + + void write_utc_offset(long offset, numeric_system ns) { + if (offset < 0) { + *out_++ = '-'; + offset = -offset; + } else { + *out_++ = '+'; + } + offset /= 60; + write2(static_cast(offset / 60)); + if (ns != numeric_system::standard) *out_++ = ':'; + write2(static_cast(offset % 60)); + } + template ::value)> + void format_utc_offset_impl(const T& tm, numeric_system ns) { + write_utc_offset(tm.tm_gmtoff, ns); + } + template ::value)> + void format_utc_offset_impl(const T& tm, numeric_system ns) { +#if defined(_WIN32) && defined(_UCRT) +# if FMT_USE_TZSET + tzset_once(); +# endif + long offset = 0; + _get_timezone(&offset); + if (tm.tm_isdst) { + long dstbias = 0; + _get_dstbias(&dstbias); + offset += dstbias; + } + write_utc_offset(-offset, ns); +#else + if (ns == numeric_system::standard) return format_localized('z'); + + // Extract timezone offset from timezone conversion functions. + std::tm gtm = tm; + std::time_t gt = std::mktime(>m); + std::tm ltm = gmtime(gt); + std::time_t lt = std::mktime(<m); + long offset = gt - lt; + write_utc_offset(offset, ns); +#endif + } + + template ::value)> + void format_tz_name_impl(const T& tm) { + if (is_classic_) + out_ = write_tm_str(out_, tm.tm_zone, loc_); + else + format_localized('Z'); + } + template ::value)> + void format_tz_name_impl(const T&) { + format_localized('Z'); + } + + void format_localized(char format, char modifier = 0) { + out_ = write(out_, tm_, loc_, format, modifier); + } + + public: + tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm, + const Duration* subsecs = nullptr) + : loc_(loc), + is_classic_(loc_ == get_classic_locale()), + out_(out), + subsecs_(subsecs), + tm_(tm) {} + + auto out() const -> OutputIt { return out_; } + + FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { + out_ = copy(begin, end, out_); + } + + void on_abbr_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_short_name(tm_wday())); + else + format_localized('a'); + } + void on_full_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_full_name(tm_wday())); + else + format_localized('A'); + } + void on_dec0_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday()); + format_localized('w', 'O'); + } + void on_dec1_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write1(wday == 0 ? days_per_week : wday); + } else { + format_localized('u', 'O'); + } + } + + void on_abbr_month() { + if (is_classic_) + out_ = write(out_, tm_mon_short_name(tm_mon())); + else + format_localized('b'); + } + void on_full_month() { + if (is_classic_) + out_ = write(out_, tm_mon_full_name(tm_mon())); + else + format_localized('B'); + } + + void on_datetime(numeric_system ns) { + if (is_classic_) { + on_abbr_weekday(); + *out_++ = ' '; + on_abbr_month(); + *out_++ = ' '; + on_day_of_month(numeric_system::standard, pad_type::space); + *out_++ = ' '; + on_iso_time(); + *out_++ = ' '; + on_year(numeric_system::standard); + } else { + format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); + } + } + void on_loc_date(numeric_system ns) { + if (is_classic_) + on_us_date(); + else + format_localized('x', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_loc_time(numeric_system ns) { + if (is_classic_) + on_iso_time(); + else + format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_us_date() { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_mon() + 1), + to_unsigned(tm_mday()), + to_unsigned(split_year_lower(tm_year())), '/'); + out_ = copy(std::begin(buf), std::end(buf), out_); + } + void on_iso_date() { + auto year = tm_year(); + char buf[10]; + size_t offset = 0; + if (year >= 0 && year < 10000) { + copy2(buf, digits2(static_cast(year / 100))); + } else { + offset = 4; + write_year_extended(year); + year = 0; + } + write_digit2_separated(buf + 2, static_cast(year % 100), + to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), + '-'); + out_ = copy(std::begin(buf) + offset, std::end(buf), out_); + } + + void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } + void on_tz_name() { format_tz_name_impl(tm_); } + + void on_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write_year(tm_year()); + format_localized('Y', 'E'); + } + void on_short_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(split_year_lower(tm_year())); + format_localized('y', 'O'); + } + void on_offset_year() { + if (is_classic_) return write2(split_year_lower(tm_year())); + format_localized('y', 'E'); + } + + void on_century(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto year = tm_year(); + auto upper = year / 100; + if (year >= -99 && year < 0) { + // Zero upper on negative year. + *out_++ = '-'; + *out_++ = '0'; + } else if (upper >= 0 && upper < 100) { + write2(static_cast(upper)); + } else { + out_ = write(out_, upper); + } + } else { + format_localized('C', 'E'); + } + } + + void on_dec_month(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mon() + 1); + format_localized('m', 'O'); + } + + void on_dec0_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week, + pad); + format_localized('U', 'O'); + } + void on_dec1_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write2((tm_yday() + days_per_week - + (wday == 0 ? (days_per_week - 1) : (wday - 1))) / + days_per_week, + pad); + } else { + format_localized('W', 'O'); + } + } + void on_iso_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_iso_week_of_year(), pad); + format_localized('V', 'O'); + } + + void on_iso_week_based_year() { write_year(tm_iso_week_year()); } + void on_iso_week_based_short_year() { + write2(split_year_lower(tm_iso_week_year())); + } + + void on_day_of_year() { + auto yday = tm_yday() + 1; + write1(yday / 100); + write2(yday % 100); + } + void on_day_of_month(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mday(), pad); + format_localized('d', 'O'); + } + + void on_24_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour(), pad); + format_localized('H', 'O'); + } + void on_12_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour12(), pad); + format_localized('I', 'O'); + } + void on_minute(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_min(), pad); + format_localized('M', 'O'); + } + + void on_second(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + write2(tm_sec(), pad); + if (subsecs_) { + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, *subsecs_); + if (buf.size() > 1) { + // Remove the leading "0", write something like ".123". + out_ = std::copy(buf.begin() + 1, buf.end(), out_); + } + } else { + write_fractional_seconds(out_, *subsecs_); + } + } + } else { + // Currently no formatting of subseconds when a locale is set. + format_localized('S', 'O'); + } + } + + void on_12_hour_time() { + if (is_classic_) { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_hour12()), + to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); + out_ = copy(std::begin(buf), std::end(buf), out_); + *out_++ = ' '; + on_am_pm(); + } else { + format_localized('r'); + } + } + void on_24_hour_time() { + write2(tm_hour()); + *out_++ = ':'; + write2(tm_min()); + } + void on_iso_time() { + on_24_hour_time(); + *out_++ = ':'; + on_second(numeric_system::standard, pad_type::zero); + } + + void on_am_pm() { + if (is_classic_) { + *out_++ = tm_hour() < 12 ? 'A' : 'P'; + *out_++ = 'M'; + } else { + format_localized('p'); + } + } + + // These apply to chrono durations but not tm. + void on_duration_value() {} + void on_duration_unit() {} +}; + +struct chrono_format_checker : null_chrono_spec_handler { + bool has_precision_integral = false; + + FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_day_of_year() {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_duration_value() const { + if (has_precision_integral) { + FMT_THROW(format_error("precision not allowed for this argument type")); + } + } + FMT_CONSTEXPR void on_duration_unit() {} +}; + +template ::value&& has_isfinite::value)> +inline auto isfinite(T) -> bool { + return true; } template ::value)> -inline T mod(T x, int y) { +inline auto mod(T x, int y) -> T { return x % static_cast(y); } template ::value)> -inline T mod(T x, int y) { +inline auto mod(T x, int y) -> T { return std::fmod(x, static_cast(y)); } @@ -902,74 +1698,52 @@ template struct make_unsigned_or_unchanged { using type = typename std::make_unsigned::type; }; -#if FMT_SAFE_DURATION_CAST -// throwing version of safe_duration_cast -template -To fmt_safe_duration_cast(std::chrono::duration from) { - int ec; - To to = safe_duration_cast::safe_duration_cast(from, ec); - if (ec) FMT_THROW(format_error("cannot format duration")); - return to; -} -#endif - template ::value)> -inline std::chrono::duration get_milliseconds( - std::chrono::duration d) { +inline auto get_milliseconds(std::chrono::duration d) + -> std::chrono::duration { // this may overflow and/or the result may not fit in the // target type. #if FMT_SAFE_DURATION_CAST using CommonSecondsType = typename std::common_type::type; - const auto d_as_common = fmt_safe_duration_cast(d); + const auto d_as_common = fmt_duration_cast(d); const auto d_as_whole_seconds = - fmt_safe_duration_cast(d_as_common); + fmt_duration_cast(d_as_common); // this conversion should be nonproblematic const auto diff = d_as_common - d_as_whole_seconds; const auto ms = - fmt_safe_duration_cast>(diff); + fmt_duration_cast>(diff); return ms; #else - auto s = std::chrono::duration_cast(d); - return std::chrono::duration_cast(d - s); + auto s = fmt_duration_cast(d); + return fmt_duration_cast(d - s); #endif } -template ::value)> -inline std::chrono::duration get_milliseconds( - std::chrono::duration d) { - using common_type = typename std::common_type::type; - auto ms = mod(d.count() * static_cast(Period::num) / - static_cast(Period::den) * 1000, - 1000); - return std::chrono::duration(static_cast(ms)); -} - template ::value)> -OutputIt format_duration_value(OutputIt out, Rep val, int) { +auto format_duration_value(OutputIt out, Rep val, int) -> OutputIt { return write(out, val); } template ::value)> -OutputIt format_duration_value(OutputIt out, Rep val, int precision) { - auto specs = basic_format_specs(); +auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt { + auto specs = format_specs(); specs.precision = precision; - specs.type = precision > 0 ? presentation_type::fixed_lower - : presentation_type::general_lower; + specs.type = + precision >= 0 ? presentation_type::fixed : presentation_type::general; return write(out, val, specs); } template -OutputIt copy_unit(string_view unit, OutputIt out, Char) { +auto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt { return std::copy(unit.begin(), unit.end(), out); } template -OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { +auto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt { // This works when wchar_t is UTF-32 because units only contain characters // that have the same representation in UTF-16 and UTF-32. utf8_to_utf16 u(unit); @@ -977,7 +1751,7 @@ OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { } template -OutputIt format_duration_unit(OutputIt out) { +auto format_duration_unit(OutputIt out) -> OutputIt { if (const char* unit = get_units()) return copy_unit(string_view(unit), out, Char()); *out++ = '['; @@ -991,6 +1765,28 @@ OutputIt format_duration_unit(OutputIt out) { return out; } +class get_locale { + private: + union { + std::locale locale_; + }; + bool has_locale_ = false; + + public: + get_locale(bool localized, locale_ref loc) : has_locale_(localized) { +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + if (localized) + ::new (&locale_) std::locale(loc.template get()); +#endif + } + ~get_locale() { + if (has_locale_) locale_.~locale(); + } + operator const std::locale&() const { + return has_locale_ ? locale_ : get_classic_locale(); + } +}; + template struct chrono_formatter { @@ -1009,9 +1805,10 @@ struct chrono_formatter { bool negative; using char_type = typename FormatContext::char_type; + using tm_writer_type = tm_writer; - explicit chrono_formatter(FormatContext& ctx, OutputIt o, - std::chrono::duration d) + chrono_formatter(FormatContext& ctx, OutputIt o, + std::chrono::duration d) : context(ctx), out(o), val(static_cast(d.count())), @@ -1023,18 +1820,12 @@ struct chrono_formatter { // this may overflow and/or the result may not fit in the // target type. -#if FMT_SAFE_DURATION_CAST // might need checked conversion (rep!=Rep) - auto tmpval = std::chrono::duration(val); - s = fmt_safe_duration_cast(tmpval); -#else - s = std::chrono::duration_cast( - std::chrono::duration(val)); -#endif + s = fmt_duration_cast(std::chrono::duration(val)); } // returns true if nan or inf, writes to out. - bool handle_nan_inf() { + auto handle_nan_inf() -> bool { if (isfinite(val)) { return false; } @@ -1051,17 +1842,22 @@ struct chrono_formatter { return true; } - Rep hour() const { return static_cast(mod((s.count() / 3600), 24)); } + auto days() const -> Rep { return static_cast(s.count() / 86400); } + auto hour() const -> Rep { + return static_cast(mod((s.count() / 3600), 24)); + } - Rep hour12() const { + auto hour12() const -> Rep { Rep hour = static_cast(mod((s.count() / 3600), 12)); return hour <= 0 ? 12 : hour; } - Rep minute() const { return static_cast(mod((s.count() / 60), 60)); } - Rep second() const { return static_cast(mod(s.count(), 60)); } + auto minute() const -> Rep { + return static_cast(mod((s.count() / 60), 60)); + } + auto second() const -> Rep { return static_cast(mod(s.count(), 60)); } - std::tm time() const { + auto time() const -> std::tm { auto time = std::tm(); time.tm_hour = to_nonnegative_int(hour(), 24); time.tm_min = to_nonnegative_int(minute(), 60); @@ -1076,13 +1872,15 @@ struct chrono_formatter { } } - void write(Rep value, int width) { + void write(Rep value, int width, pad_type pad = pad_type::zero) { write_sign(); if (isnan(value)) return write_nan(); uint32_or_64_or_128_t n = to_unsigned(to_nonnegative_int(value, max_value())); int num_digits = detail::count_digits(n); - if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); + if (width > num_digits) { + out = detail::write_padding(out, pad, width - num_digits); + } out = format_decimal(out, n, num_digits).end; } @@ -1090,11 +1888,13 @@ struct chrono_formatter { void write_pinf() { std::copy_n("inf", 3, out); } void write_ninf() { std::copy_n("-inf", 4, out); } - void format_localized(const tm& time, char format, char modifier = 0) { + template + void format_tm(const tm& time, Callback cb, Args... args) { if (isnan(val)) return write_nan(); - const auto& loc = localized ? context.locale().template get() - : std::locale::classic(); - out = detail::write(out, time, loc, format, modifier); + get_locale loc(localized, context.locale()); + auto w = tm_writer_type(loc, out, time); + (w.*cb)(args...); + out = w.out(); } void on_text(const char_type* begin, const char_type* end) { @@ -1113,64 +1913,80 @@ struct chrono_formatter { void on_loc_time(numeric_system) {} void on_us_date() {} void on_iso_date() {} - void on_utc_offset() {} + void on_utc_offset(numeric_system) {} void on_tz_name() {} + void on_year(numeric_system) {} + void on_short_year(numeric_system) {} + void on_offset_year() {} + void on_century(numeric_system) {} + void on_iso_week_based_year() {} + void on_iso_week_based_short_year() {} + void on_dec_month(numeric_system) {} + void on_dec0_week_of_year(numeric_system, pad_type) {} + void on_dec1_week_of_year(numeric_system, pad_type) {} + void on_iso_week_of_year(numeric_system, pad_type) {} + void on_day_of_month(numeric_system, pad_type) {} + + void on_day_of_year() { + if (handle_nan_inf()) return; + write(days(), 0); + } - void on_24_hour(numeric_system ns) { + void on_24_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour(), 2); + if (ns == numeric_system::standard) return write(hour(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour(), 24); - format_localized(time, 'H', 'O'); + format_tm(time, &tm_writer_type::on_24_hour, ns, pad); } - void on_12_hour(numeric_system ns) { + void on_12_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour12(), 2); + if (ns == numeric_system::standard) return write(hour12(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour12(), 12); - format_localized(time, 'I', 'O'); + format_tm(time, &tm_writer_type::on_12_hour, ns, pad); } - void on_minute(numeric_system ns) { + void on_minute(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(minute(), 2); + if (ns == numeric_system::standard) return write(minute(), 2, pad); auto time = tm(); time.tm_min = to_nonnegative_int(minute(), 60); - format_localized(time, 'M', 'O'); + format_tm(time, &tm_writer_type::on_minute, ns, pad); } - void on_second(numeric_system ns) { + void on_second(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) { - write(second(), 2); -#if FMT_SAFE_DURATION_CAST - // convert rep->Rep - using duration_rep = std::chrono::duration; - using duration_Rep = std::chrono::duration; - auto tmpval = fmt_safe_duration_cast(duration_rep{val}); -#else - auto tmpval = std::chrono::duration(val); -#endif - auto ms = get_milliseconds(tmpval); - if (ms != std::chrono::milliseconds(0)) { - *out++ = '.'; - write(ms.count(), 3); + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, std::chrono::duration(val), + precision); + if (negative) *out++ = '-'; + if (buf.size() < 2 || buf[1] == '.') { + out = detail::write_padding(out, pad); + } + out = std::copy(buf.begin(), buf.end(), out); + } else { + write(second(), 2, pad); + write_fractional_seconds( + out, std::chrono::duration(val), precision); } return; } auto time = tm(); time.tm_sec = to_nonnegative_int(second(), 60); - format_localized(time, 'S', 'O'); + format_tm(time, &tm_writer_type::on_second, ns, pad); } void on_12_hour_time() { if (handle_nan_inf()) return; - format_localized(time(), 'r'); + format_tm(time(), &tm_writer_type::on_12_hour_time); } void on_24_hour_time() { @@ -1189,12 +2005,12 @@ struct chrono_formatter { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; - write(second(), 2); + on_second(numeric_system::standard, pad_type::zero); } void on_am_pm() { if (handle_nan_inf()) return; - format_localized(time(), 'p'); + format_tm(time(), &tm_writer_type::on_am_pm); } void on_duration_value() { @@ -1208,168 +2024,409 @@ struct chrono_formatter { } }; -FMT_END_DETAIL_NAMESPACE +} // namespace detail #if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907 using weekday = std::chrono::weekday; +using day = std::chrono::day; +using month = std::chrono::month; +using year = std::chrono::year; +using year_month_day = std::chrono::year_month_day; #else // A fallback version of weekday. class weekday { private: - unsigned char value; + unsigned char value_; public: weekday() = default; - explicit constexpr weekday(unsigned wd) noexcept - : value(static_cast(wd != 7 ? wd : 0)) {} - constexpr unsigned c_encoding() const noexcept { return value; } + constexpr explicit weekday(unsigned wd) noexcept + : value_(static_cast(wd != 7 ? wd : 0)) {} + constexpr auto c_encoding() const noexcept -> unsigned { return value_; } +}; + +class day { + private: + unsigned char value_; + + public: + day() = default; + constexpr explicit day(unsigned d) noexcept + : value_(static_cast(d)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; + +class month { + private: + unsigned char value_; + + public: + month() = default; + constexpr explicit month(unsigned m) noexcept + : value_(static_cast(m)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; + +class year { + private: + int value_; + + public: + year() = default; + constexpr explicit year(int y) noexcept : value_(y) {} + constexpr explicit operator int() const noexcept { return value_; } }; -class year_month_day {}; +class year_month_day { + private: + fmt::year year_; + fmt::month month_; + fmt::day day_; + + public: + year_month_day() = default; + constexpr year_month_day(const year& y, const month& m, const day& d) noexcept + : year_(y), month_(m), day_(d) {} + constexpr auto year() const noexcept -> fmt::year { return year_; } + constexpr auto month() const noexcept -> fmt::month { return month_; } + constexpr auto day() const noexcept -> fmt::day { return day_; } +}; #endif -// A rudimentary weekday formatter. -template <> struct formatter { +template +struct formatter : private formatter { private: - bool localized = false; + bool localized_ = false; + bool use_tm_formatter_ = false; public: - FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin != end && *begin == 'L') { - ++begin; - localized = true; + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; } - return begin; + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; } - auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) { + template + auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_wday = static_cast(wd.c_encoding()); - const auto& loc = localized ? ctx.locale().template get() - : std::locale::classic(); - return detail::write(ctx.out(), time, loc, 'a'); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(localized_, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_weekday(); + return w.out(); } }; -template -struct formatter, Char> { +template +struct formatter : private formatter { private: - basic_format_specs specs; - int precision = -1; - using arg_ref_type = detail::arg_ref; - arg_ref_type width_ref; - arg_ref_type precision_ref; - bool localized = false; - basic_string_view format_str; - using duration = std::chrono::duration; + bool use_tm_formatter_ = false; - struct spec_handler { - formatter& f; - basic_format_parse_context& context; - basic_string_view format_str; + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } - template FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { - context.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } + template + auto format(day d, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mday = static_cast(static_cast(d)); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_day_of_month(detail::numeric_system::standard, detail::pad_type::zero); + return w.out(); + } +}; - FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view arg_id) { - context.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } +template +struct formatter : private formatter { + private: + bool localized_ = false; + bool use_tm_formatter_ = false; - FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) { - return arg_ref_type(context.next_arg_id()); + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; } + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } - void on_error(const char* msg) { FMT_THROW(format_error(msg)); } - FMT_CONSTEXPR void on_fill(basic_string_view fill) { - f.specs.fill = fill; - } - FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; } - FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; } - FMT_CONSTEXPR void on_precision(int _precision) { - f.precision = _precision; - } - FMT_CONSTEXPR void end_precision() {} + template + auto format(month m, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mon = static_cast(static_cast(m)) - 1; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(localized_, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_month(); + return w.out(); + } +}; - template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - f.width_ref = make_arg_ref(arg_id); - } +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; - template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - f.precision_ref = make_arg_ref(arg_id); - } - }; + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } - using iterator = typename basic_format_parse_context::iterator; - struct parse_range { - iterator begin; - iterator end; - }; + template + auto format(year y, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(y) - 1900; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_year(detail::numeric_system::standard); + return w.out(); + } +}; - FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context& ctx) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin == end || *begin == '}') return {begin, begin}; - spec_handler handler{*this, ctx, format_str}; - begin = detail::parse_align(begin, end, handler); - if (begin == end) return {begin, begin}; - begin = detail::parse_width(begin, end, handler); - if (begin == end) return {begin, begin}; - if (*begin == '.') { - if (std::is_floating_point::value) - begin = detail::parse_precision(begin, end, handler); - else - handler.on_error("precision not allowed for this argument type"); - } - if (begin != end && *begin == 'L') { - ++begin; - localized = true; - } - end = parse_chrono_format(begin, end, detail::chrono_format_checker()); - return {begin, end}; +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; } + template + auto format(year_month_day val, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(val.year()) - 1900; + time.tm_mon = static_cast(static_cast(val.month())) - 1; + time.tm_mday = static_cast(static_cast(val.day())); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(true, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_iso_date(); + return w.out(); + } +}; + +template +struct formatter, Char> { + private: + format_specs specs_; + detail::arg_ref width_ref_; + detail::arg_ref precision_ref_; + bool localized_ = false; + basic_string_view format_str_; + public: FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()) { - auto range = do_parse(ctx); - format_str = basic_string_view( - &*range.begin, detail::to_unsigned(range.end - range.begin)); - return range.end; + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + if (it == end) return it; + + auto checker = detail::chrono_format_checker(); + if (*it == '.') { + checker.has_precision_integral = !std::is_floating_point::value; + it = detail::parse_precision(it, end, specs_.precision, precision_ref_, + ctx); + } + if (it != end && *it == 'L') { + localized_ = true; + ++it; + } + end = detail::parse_chrono_format(it, end, checker); + format_str_ = {it, detail::to_unsigned(end - it)}; + return end; } template - auto format(const duration& d, FormatContext& ctx) const + auto format(std::chrono::duration d, FormatContext& ctx) const -> decltype(ctx.out()) { - auto specs_copy = specs; - auto precision_copy = precision; - auto begin = format_str.begin(), end = format_str.end(); + auto specs = specs_; + auto precision = specs.precision; + specs.precision = -1; + auto begin = format_str_.begin(), end = format_str_.end(); // As a possible future optimization, we could avoid extra copying if width // is not specified. - basic_memory_buffer buf; + auto buf = basic_memory_buffer(); auto out = std::back_inserter(buf); - detail::handle_dynamic_spec(specs_copy.width, - width_ref, ctx); - detail::handle_dynamic_spec(precision_copy, - precision_ref, ctx); + detail::handle_dynamic_spec(specs.width, width_ref_, + ctx); + detail::handle_dynamic_spec(precision, + precision_ref_, ctx); if (begin == end || *begin == '}') { - out = detail::format_duration_value(out, d.count(), precision_copy); + out = detail::format_duration_value(out, d.count(), precision); detail::format_duration_unit(out); } else { - detail::chrono_formatter f( - ctx, out, d); - f.precision = precision_copy; - f.localized = localized; + using chrono_formatter = + detail::chrono_formatter; + auto f = chrono_formatter(ctx, out, d); + f.precision = precision; + f.localized = localized_; detail::parse_chrono_format(begin, end, f); } return detail::write( - ctx.out(), basic_string_view(buf.data(), buf.size()), specs_copy); + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } +}; + +template +struct formatter, + Char> : formatter { + FMT_CONSTEXPR formatter() { + this->format_str_ = detail::string_literal{}; + } + + template + auto format(std::chrono::time_point val, + FormatContext& ctx) const -> decltype(ctx.out()) { + std::tm tm = gmtime(val); + using period = typename Duration::period; + if (detail::const_check( + period::num == 1 && period::den == 1 && + !std::is_floating_point::value)) { + return formatter::format(tm, ctx); + } + Duration epoch = val.time_since_epoch(); + Duration subsecs = detail::fmt_duration_cast( + epoch - detail::fmt_duration_cast(epoch)); + if (subsecs.count() < 0) { + auto second = + detail::fmt_duration_cast(std::chrono::seconds(1)); + if (tm.tm_sec != 0) + --tm.tm_sec; + else + tm = gmtime(val - second); + subsecs += detail::fmt_duration_cast(std::chrono::seconds(1)); + } + return formatter::do_format(tm, ctx, &subsecs); + } +}; + +#if FMT_USE_LOCAL_TIME +template +struct formatter, Char> + : formatter { + FMT_CONSTEXPR formatter() { + this->format_str_ = detail::string_literal{}; + } + + template + auto format(std::chrono::local_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + using period = typename Duration::period; + if (period::num != 1 || period::den != 1 || + std::is_floating_point::value) { + const auto epoch = val.time_since_epoch(); + const auto subsecs = detail::fmt_duration_cast( + epoch - detail::fmt_duration_cast(epoch)); + + return formatter::do_format(localtime(val), ctx, &subsecs); + } + + return formatter::format(localtime(val), ctx); + } +}; +#endif + +#if FMT_USE_UTC_TIME +template +struct formatter, + Char> + : formatter, + Char> { + template + auto format(std::chrono::time_point val, + FormatContext& ctx) const -> decltype(ctx.out()) { + return formatter< + std::chrono::time_point, + Char>::format(std::chrono::utc_clock::to_sys(val), ctx); + } +}; +#endif + +template struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + + protected: + basic_string_view format_str_; + + template + auto do_format(const std::tm& tm, FormatContext& ctx, + const Duration* subsecs) const -> decltype(ctx.out()) { + auto specs = specs_; + auto buf = basic_memory_buffer(); + auto out = std::back_inserter(buf); + detail::handle_dynamic_spec(specs.width, width_ref_, + ctx); + + auto loc_ref = ctx.locale(); + detail::get_locale loc(static_cast(loc_ref), loc_ref); + auto w = + detail::tm_writer(loc, out, tm, subsecs); + detail::parse_chrono_format(format_str_.begin(), format_str_.end(), w); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + if (it == end) return it; + + end = detail::parse_chrono_format(it, end, detail::tm_format_checker()); + // Replace the default format_str only if the new spec is not empty. + if (end != it) format_str_ = {it, detail::to_unsigned(end - it)}; + return end; + } + + template + auto format(const std::tm& tm, FormatContext& ctx) const + -> decltype(ctx.out()) { + return do_format(tm, ctx, nullptr); } }; -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_CHRONO_H_ diff --git a/src/3rdparty/fmt/color.h b/src/3rdparty/fmt/color.h index dfbe4829..f0e9dd94 100644 --- a/src/3rdparty/fmt/color.h +++ b/src/3rdparty/fmt/color.h @@ -10,15 +10,8 @@ #include "format.h" -// __declspec(deprecated) is broken in some MSVC versions. -#if FMT_MSC_VER -# define FMT_DEPRECATED_NONMSVC -#else -# define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED -#endif - FMT_BEGIN_NAMESPACE -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT enum class color : uint32_t { alice_blue = 0xF0F8FF, // rgb(240,248,255) @@ -210,21 +203,20 @@ struct rgb { uint8_t b; }; -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { // color is a struct of either a rgb color or a terminal color. struct color_type { - FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {} - FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true), - value{} { + FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} + FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { value.rgb_color = static_cast(rgb_color); } - FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} { + FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} { value.rgb_color = (static_cast(rgb_color.r) << 16) | (static_cast(rgb_color.g) << 8) | rgb_color.b; } - FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(), - value{} { + FMT_CONSTEXPR color_type(terminal_color term_color) noexcept + : is_rgb(), value{} { value.term_color = static_cast(term_color); } bool is_rgb; @@ -233,24 +225,21 @@ struct color_type { uint32_t rgb_color; } value; }; +} // namespace detail -FMT_END_DETAIL_NAMESPACE - -/** A text style consisting of foreground and background colors and emphasis. */ +/// A text style consisting of foreground and background colors and emphasis. class text_style { public: - FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT - : set_foreground_color(), - set_background_color(), - ems(em) {} + FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept + : set_foreground_color(), set_background_color(), ems(em) {} - FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) { + FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { if (!set_foreground_color) { set_foreground_color = rhs.set_foreground_color; foreground_color = rhs.foreground_color; } else if (rhs.set_foreground_color) { if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) - FMT_THROW(format_error("can't OR a terminal color")); + report_error("can't OR a terminal color"); foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; } @@ -259,7 +248,7 @@ class text_style { background_color = rhs.background_color; } else if (rhs.set_background_color) { if (!background_color.is_rgb || !rhs.background_color.is_rgb) - FMT_THROW(format_error("can't OR a terminal color")); + report_error("can't OR a terminal color"); background_color.value.rgb_color |= rhs.background_color.value.rgb_color; } @@ -268,49 +257,37 @@ class text_style { return *this; } - friend FMT_CONSTEXPR text_style operator|(text_style lhs, - const text_style& rhs) { + friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) + -> text_style { return lhs |= rhs; } - FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=( - const text_style& rhs) { - return and_assign(rhs); - } - - FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style - operator&(text_style lhs, const text_style& rhs) { - return lhs.and_assign(rhs); - } - - FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { return set_foreground_color; } - FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto has_background() const noexcept -> bool { return set_background_color; } - FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { return static_cast(ems) != 0; } - FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { FMT_ASSERT(has_foreground(), "no foreground specified for this style"); return foreground_color; } - FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { FMT_ASSERT(has_background(), "no background specified for this style"); return background_color; } - FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); return ems; } private: FMT_CONSTEXPR text_style(bool is_foreground, - detail::color_type text_color) FMT_NOEXCEPT - : set_foreground_color(), - set_background_color(), - ems() { + detail::color_type text_color) noexcept + : set_foreground_color(), set_background_color(), ems() { if (is_foreground) { foreground_color = text_color; set_foreground_color = true; @@ -320,36 +297,11 @@ class text_style { } } - // DEPRECATED! - FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) { - if (!set_foreground_color) { - set_foreground_color = rhs.set_foreground_color; - foreground_color = rhs.foreground_color; - } else if (rhs.set_foreground_color) { - if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) - FMT_THROW(format_error("can't AND a terminal color")); - foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color; - } - - if (!set_background_color) { - set_background_color = rhs.set_background_color; - background_color = rhs.background_color; - } else if (rhs.set_background_color) { - if (!background_color.is_rgb || !rhs.background_color.is_rgb) - FMT_THROW(format_error("can't AND a terminal color")); - background_color.value.rgb_color &= rhs.background_color.value.rgb_color; - } - - ems = static_cast(static_cast(ems) & - static_cast(rhs.ems)); - return *this; - } - - friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground) - FMT_NOEXCEPT; + friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept + -> text_style; - friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background) - FMT_NOEXCEPT; + friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept + -> text_style; detail::color_type foreground_color; detail::color_type background_color; @@ -358,26 +310,28 @@ class text_style { emphasis ems; }; -/** Creates a text style from the foreground (text) color. */ -FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT { +/// Creates a text style from the foreground (text) color. +FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept + -> text_style { return text_style(true, foreground); } -/** Creates a text style from the background color. */ -FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT { +/// Creates a text style from the background color. +FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept + -> text_style { return text_style(false, background); } -FMT_CONSTEXPR inline text_style operator|(emphasis lhs, - emphasis rhs) FMT_NOEXCEPT { +FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept + -> text_style { return text_style(lhs) | rhs; } -FMT_BEGIN_DETAIL_NAMESPACE +namespace detail { template struct ansi_color_escape { FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, - const char* esc) FMT_NOEXCEPT { + const char* esc) noexcept { // If we have a terminal color, we need to output another escape code // sequence. if (!text_color.is_rgb) { @@ -412,7 +366,7 @@ template struct ansi_color_escape { to_esc(color.b, buffer + 15, 'm'); buffer[19] = static_cast(0); } - FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT { + FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { uint8_t em_codes[num_emphases] = {}; if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1; if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2; @@ -433,11 +387,11 @@ template struct ansi_color_escape { } buffer[index++] = static_cast(0); } - FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; } + FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } - FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } - FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT { - return buffer + std::char_traits::length(buffer); + FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } + FMT_CONSTEXPR20 auto end() const noexcept -> const Char* { + return buffer + basic_string_view(buffer).size(); } private: @@ -445,63 +399,51 @@ template struct ansi_color_escape { Char buffer[7u + 3u * num_emphases + 1u]; static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, - char delimiter) FMT_NOEXCEPT { + char delimiter) noexcept { out[0] = static_cast('0' + c / 100); out[1] = static_cast('0' + c / 10 % 10); out[2] = static_cast('0' + c % 10); out[3] = static_cast(delimiter); } - static FMT_CONSTEXPR bool has_emphasis(emphasis em, - emphasis mask) FMT_NOEXCEPT { + static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept + -> bool { return static_cast(em) & static_cast(mask); } }; template -FMT_CONSTEXPR ansi_color_escape make_foreground_color( - detail::color_type foreground) FMT_NOEXCEPT { +FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept + -> ansi_color_escape { return ansi_color_escape(foreground, "\x1b[38;2;"); } template -FMT_CONSTEXPR ansi_color_escape make_background_color( - detail::color_type background) FMT_NOEXCEPT { +FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept + -> ansi_color_escape { return ansi_color_escape(background, "\x1b[48;2;"); } template -FMT_CONSTEXPR ansi_color_escape make_emphasis(emphasis em) FMT_NOEXCEPT { +FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept + -> ansi_color_escape { return ansi_color_escape(em); } -template -inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT { - std::fputs(chars, stream); -} - -template <> -inline void fputs(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT { - std::fputws(chars, stream); -} - -template inline void reset_color(FILE* stream) FMT_NOEXCEPT { - fputs("\x1b[0m", stream); -} - -template <> inline void reset_color(FILE* stream) FMT_NOEXCEPT { - fputs(L"\x1b[0m", stream); -} - -template -inline void reset_color(buffer& buffer) FMT_NOEXCEPT { +template inline void reset_color(buffer& buffer) { auto reset_color = string_view("\x1b[0m"); buffer.append(reset_color.begin(), reset_color.end()); } +template struct styled_arg : detail::view { + const T& value; + text_style style; + styled_arg(const T& v, text_style s) : value(v), style(s) {} +}; + template -void vformat_to(buffer& buf, const text_style& ts, - basic_string_view format_str, - basic_format_args>> args) { +void vformat_to( + buffer& buf, const text_style& ts, basic_string_view format_str, + basic_format_args>> args) { bool has_style = false; if (ts.has_emphasis()) { has_style = true; @@ -522,117 +464,149 @@ void vformat_to(buffer& buf, const text_style& ts, if (has_style) detail::reset_color(buf); } -FMT_END_DETAIL_NAMESPACE +} // namespace detail -template > -void vprint(std::FILE* f, const text_style& ts, const S& format, - basic_format_args>> args) { - basic_memory_buffer buf; - detail::vformat_to(buf, ts, to_string_view(format), args); - buf.push_back(Char(0)); - detail::fputs(buf.data(), f); +inline void vprint(FILE* f, const text_style& ts, string_view fmt, + format_args args) { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); } /** - \rst - Formats a string and prints it to the specified file stream using ANSI - escape sequences to specify text formatting. - - **Example**:: - - fmt::print(fmt::emphasis::bold | fg(fmt::color::red), - "Elapsed time: {0:.2f} seconds", 1.23); - \endrst + * Formats a string and prints it to the specified file stream using ANSI + * escape sequences to specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); */ -template ::value)> -void print(std::FILE* f, const text_style& ts, const S& format_str, - const Args&... args) { - vprint(f, ts, format_str, - fmt::make_args_checked(format_str, args...)); +template +void print(FILE* f, const text_style& ts, format_string fmt, + T&&... args) { + vprint(f, ts, fmt, fmt::make_format_args(args...)); } /** - \rst - Formats a string and prints it to stdout using ANSI escape sequences to - specify text formatting. - - **Example**:: - - fmt::print(fmt::emphasis::bold | fg(fmt::color::red), - "Elapsed time: {0:.2f} seconds", 1.23); - \endrst + * Formats a string and prints it to stdout using ANSI escape sequences to + * specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); */ -template ::value)> -void print(const text_style& ts, const S& format_str, const Args&... args) { - return print(stdout, ts, format_str, args...); +template +void print(const text_style& ts, format_string fmt, T&&... args) { + return print(stdout, ts, fmt, std::forward(args)...); } -template > -inline std::basic_string vformat( - const text_style& ts, const S& format_str, - basic_format_args>> args) { - basic_memory_buffer buf; - detail::vformat_to(buf, ts, to_string_view(format_str), args); +inline auto vformat(const text_style& ts, string_view fmt, format_args args) + -> std::string { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); return fmt::to_string(buf); } /** - \rst - Formats arguments and returns the result as a string using ANSI - escape sequences to specify text formatting. - - **Example**:: - - #include - std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), - "The answer is {}", 42); - \endrst -*/ -template > -inline std::basic_string format(const text_style& ts, const S& format_str, - const Args&... args) { - return fmt::vformat(ts, to_string_view(format_str), - fmt::make_args_checked(format_str, args...)); + * Formats arguments and returns the result as a string using ANSI escape + * sequences to specify text formatting. + * + * **Example**: + * + * ``` + * #include + * std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + * "The answer is {}", 42); + * ``` + */ +template +inline auto format(const text_style& ts, format_string fmt, T&&... args) + -> std::string { + return fmt::vformat(ts, fmt, fmt::make_format_args(args...)); +} + +/// Formats a string with the given text_style and writes the output to `out`. +template ::value)> +auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, + format_args args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, ts, fmt, args); + return detail::get_iterator(buf, out); } /** - Formats a string with the given text_style and writes the output to ``out``. + * Formats arguments with the given text style, writes the result to the output + * iterator `out` and returns the iterator past the end of the output range. + * + * **Example**: + * + * std::vector out; + * fmt::format_to(std::back_inserter(out), + * fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); */ -template ::value)> -OutputIt vformat_to( - OutputIt out, const text_style& ts, basic_string_view format_str, - basic_format_args>> args) { - auto&& buf = detail::get_buffer(out); - detail::vformat_to(buf, ts, format_str, args); - return detail::get_iterator(buf); +template ::value)> +inline auto format_to(OutputIt out, const text_style& ts, + format_string fmt, T&&... args) -> OutputIt { + return vformat_to(out, ts, fmt, fmt::make_format_args(args...)); } +template +struct formatter, Char> : formatter { + template + auto format(const detail::styled_arg& arg, FormatContext& ctx) const + -> decltype(ctx.out()) { + const auto& ts = arg.style; + const auto& value = arg.value; + auto out = ctx.out(); + + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = detail::make_emphasis(ts.get_emphasis()); + out = std::copy(emphasis.begin(), emphasis.end(), out); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = + detail::make_foreground_color(ts.get_foreground()); + out = std::copy(foreground.begin(), foreground.end(), out); + } + if (ts.has_background()) { + has_style = true; + auto background = + detail::make_background_color(ts.get_background()); + out = std::copy(background.begin(), background.end(), out); + } + out = formatter::format(value, ctx); + if (has_style) { + auto reset_color = string_view("\x1b[0m"); + out = std::copy(reset_color.begin(), reset_color.end(), out); + } + return out; + } +}; + /** - \rst - Formats arguments with the given text_style, writes the result to the output - iterator ``out`` and returns the iterator past the end of the output range. - - **Example**:: - - std::vector out; - fmt::format_to(std::back_inserter(out), - fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); - \endrst -*/ -template >::value&& - detail::is_string::value> -inline auto format_to(OutputIt out, const text_style& ts, const S& format_str, - Args&&... args) -> - typename std::enable_if::type { - return vformat_to(out, ts, to_string_view(format_str), - fmt::make_args_checked(format_str, args...)); + * Returns an argument that will be formatted using ANSI escape sequences, + * to be used in a formatting function. + * + * **Example**: + * + * fmt::print("Elapsed time: {0:.2f} seconds", + * fmt::styled(1.23, fmt::fg(fmt::color::green) | + * fmt::bg(fmt::color::blue))); + */ +template +FMT_CONSTEXPR auto styled(const T& value, text_style ts) + -> detail::styled_arg> { + return detail::styled_arg>{value, ts}; } -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COLOR_H_ diff --git a/src/3rdparty/fmt/compile.h b/src/3rdparty/fmt/compile.h index 97e1240e..d77de358 100644 --- a/src/3rdparty/fmt/compile.h +++ b/src/3rdparty/fmt/compile.h @@ -8,178 +8,62 @@ #ifndef FMT_COMPILE_H_ #define FMT_COMPILE_H_ +#ifndef FMT_IMPORT_STD +# include // std::back_inserter +#endif + #include "format.h" FMT_BEGIN_NAMESPACE -namespace detail { - -// An output iterator that counts the number of objects written to it and -// discards them. -class counting_iterator { - private: - size_t count_; - - public: - using iterator_category = std::output_iterator_tag; - using difference_type = std::ptrdiff_t; - using pointer = void; - using reference = void; - using _Unchecked_type = counting_iterator; // Mark iterator as checked. - - struct value_type { - template void operator=(const T&) {} - }; - - counting_iterator() : count_(0) {} - size_t count() const { return count_; } - - counting_iterator& operator++() { - ++count_; - return *this; - } - counting_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - friend counting_iterator operator+(counting_iterator it, difference_type n) { - it.count_ += static_cast(n); - return it; - } +// A compile-time string which is compiled into fast formatting code. +FMT_EXPORT class compiled_string {}; - value_type operator*() const { return {}; } -}; +namespace detail { -template -inline counting_iterator copy_str(InputIt begin, InputIt end, - counting_iterator it) { +template +FMT_CONSTEXPR inline auto copy(InputIt begin, InputIt end, counting_iterator it) + -> counting_iterator { return it + (end - begin); } -template class truncating_iterator_base { - protected: - OutputIt out_; - size_t limit_; - size_t count_ = 0; - - truncating_iterator_base() : out_(), limit_(0) {} - - truncating_iterator_base(OutputIt out, size_t limit) - : out_(out), limit_(limit) {} - - public: - using iterator_category = std::output_iterator_tag; - using value_type = typename std::iterator_traits::value_type; - using difference_type = std::ptrdiff_t; - using pointer = void; - using reference = void; - using _Unchecked_type = - truncating_iterator_base; // Mark iterator as checked. - - OutputIt base() const { return out_; } - size_t count() const { return count_; } -}; - -// An output iterator that truncates the output and counts the number of objects -// written to it. -template ::value_type>::type> -class truncating_iterator; - -template -class truncating_iterator - : public truncating_iterator_base { - mutable typename truncating_iterator_base::value_type blackhole_; - - public: - using value_type = typename truncating_iterator_base::value_type; - - truncating_iterator() = default; - - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} - - truncating_iterator& operator++() { - if (this->count_++ < this->limit_) ++this->out_; - return *this; - } - - truncating_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - value_type& operator*() const { - return this->count_ < this->limit_ ? *this->out_ : blackhole_; - } -}; - -template -class truncating_iterator - : public truncating_iterator_base { - public: - truncating_iterator() = default; - - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} - - template truncating_iterator& operator=(T val) { - if (this->count_++ < this->limit_) *this->out_++ = val; - return *this; - } - - truncating_iterator& operator++() { return *this; } - truncating_iterator& operator++(int) { return *this; } - truncating_iterator& operator*() { return *this; } -}; - -// A compile-time string which is compiled into fast formatting code. -class compiled_string {}; - template struct is_compiled_string : std::is_base_of {}; /** - \rst - Converts a string literal *s* into a format string that will be parsed at - compile time and converted into efficient formatting code. Requires C++17 - ``constexpr if`` compiler support. - - **Example**:: - - // Converts 42 into std::string using the most efficient method and no - // runtime format string processing. - std::string s = fmt::format(FMT_COMPILE("{}"), 42); - \endrst + * Converts a string literal `s` into a format string that will be parsed at + * compile time and converted into efficient formatting code. Requires C++17 + * `constexpr if` compiler support. + * + * **Example**: + * + * // Converts 42 into std::string using the most efficient method and no + * // runtime format string processing. + * std::string s = fmt::format(FMT_COMPILE("{}"), 42); */ -#ifdef __cpp_if_constexpr -# define FMT_COMPILE(s) \ - FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit) +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) +# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string, explicit) #else # define FMT_COMPILE(s) FMT_STRING(s) #endif -#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +#if FMT_USE_NONTYPE_TEMPLATE_ARGS template Str> struct udl_compiled_string : compiled_string { using char_type = Char; - constexpr operator basic_string_view() const { + explicit constexpr operator basic_string_view() const { return {Str.data, N - 1}; } }; #endif template -const T& first(const T& value, const Tail&...) { +auto first(const T& value, const Tail&...) -> const T& { return value; } -#ifdef __cpp_if_constexpr +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) template struct type_list {}; // Returns a reference to the argument at index N from [first, rest...]. @@ -190,7 +74,7 @@ constexpr const auto& get([[maybe_unused]] const T& first, if constexpr (N == 0) return first; else - return get(rest...); + return detail::get(rest...); } template @@ -202,7 +86,8 @@ constexpr int get_arg_index_by_name(basic_string_view name, template struct get_type_impl; template struct get_type_impl> { - using type = remove_cvref_t(std::declval()...))>; + using type = + remove_cvref_t(std::declval()...))>; }; template @@ -235,14 +120,15 @@ template struct code_unit { template constexpr OutputIt format(OutputIt out, const Args&...) const { - return write(out, value); + *out++ = value; + return out; } }; // This ensures that the argument type is convertible to `const T&`. template constexpr const T& get_arg_checked(const Args&... args) { - const auto& arg = get(args...); + const auto& arg = detail::get(args...); if constexpr (detail::is_named_arg>()) { return arg.value; } else { @@ -259,7 +145,12 @@ template struct field { template constexpr OutputIt format(OutputIt out, const Args&... args) const { - return write(out, get_arg_checked(args...)); + const T& arg = get_arg_checked(args...); + if constexpr (std::is_convertible>::value) { + auto s = basic_string_view(arg); + return copy(s.begin(), s.end(), out); + } + return write(out, arg); } }; @@ -347,13 +238,12 @@ constexpr size_t parse_text(basic_string_view str, size_t pos) { } template -constexpr auto compile_format_string(S format_str); +constexpr auto compile_format_string(S fmt); template -constexpr auto parse_tail(T head, S format_str) { - if constexpr (POS != - basic_string_view(format_str).size()) { - constexpr auto tail = compile_format_string(format_str); +constexpr auto parse_tail(T head, S fmt) { + if constexpr (POS != basic_string_view(fmt).size()) { + constexpr auto tail = compile_format_string(fmt); if constexpr (std::is_same, unknown_format>()) return tail; @@ -370,36 +260,35 @@ template struct parse_specs_result { int next_arg_id; }; -constexpr int manual_indexing_id = -1; +enum { manual_indexing_id = -1 }; template constexpr parse_specs_result parse_specs(basic_string_view str, size_t pos, int next_arg_id) { str.remove_prefix(pos); - auto ctx = basic_format_parse_context(str, {}, next_arg_id); + auto ctx = + compile_parse_context(str, max_value(), nullptr, next_arg_id); auto f = formatter(); auto end = f.parse(ctx); - return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1, + return {f, pos + fmt::detail::to_unsigned(end - str.data()), next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; } template struct arg_id_handler { arg_ref arg_id; - constexpr int operator()() { + constexpr int on_auto() { FMT_ASSERT(false, "handler cannot be used with automatic indexing"); return 0; } - constexpr int operator()(int id) { + constexpr int on_index(int id) { arg_id = arg_ref(id); return 0; } - constexpr int operator()(basic_string_view id) { + constexpr int on_name(basic_string_view id) { arg_id = arg_ref(id); return 0; } - - constexpr void on_error(const char* message) { FMT_THROW(format_error(message)); } }; template struct parse_arg_id_result { @@ -425,43 +314,48 @@ struct field_type::value>> { template -constexpr auto parse_replacement_field_then_tail(S format_str) { +constexpr auto parse_replacement_field_then_tail(S fmt) { using char_type = typename S::char_type; - constexpr auto str = basic_string_view(format_str); + constexpr auto str = basic_string_view(fmt); constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); if constexpr (c == '}') { return parse_tail( - field::type, ARG_INDEX>(), - format_str); - } else if constexpr (c == ':') { + field::type, ARG_INDEX>(), fmt); + } else if constexpr (c != ':') { + FMT_THROW(format_error("expected ':'")); + } else { constexpr auto result = parse_specs::type>( str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); - return parse_tail( - spec_field::type, ARG_INDEX>{ - result.fmt}, - format_str); + if constexpr (result.end >= str.size() || str[result.end] != '}') { + FMT_THROW(format_error("expected '}'")); + return 0; + } else { + return parse_tail( + spec_field::type, ARG_INDEX>{ + result.fmt}, + fmt); + } } } // Compiles a non-empty format string and returns the compiled representation // or unknown_format() on unrecognized input. template -constexpr auto compile_format_string(S format_str) { +constexpr auto compile_format_string(S fmt) { using char_type = typename S::char_type; - constexpr auto str = basic_string_view(format_str); + constexpr auto str = basic_string_view(fmt); if constexpr (str[POS] == '{') { if constexpr (POS + 1 == str.size()) FMT_THROW(format_error("unmatched '{' in format string")); if constexpr (str[POS + 1] == '{') { - return parse_tail(make_text(str, POS, 1), format_str); + return parse_tail(make_text(str, POS, 1), fmt); } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { static_assert(ID != manual_indexing_id, "cannot switch from manual to automatic argument indexing"); constexpr auto next_id = ID != manual_indexing_id ? ID + 1 : manual_indexing_id; return parse_replacement_field_then_tail, Args, - POS + 1, ID, next_id>( - format_str); + POS + 1, ID, next_id>(fmt); } else { constexpr auto arg_id_result = parse_arg_id(str.data() + POS + 1, str.data() + str.size()); @@ -477,62 +371,57 @@ constexpr auto compile_format_string(S format_str) { return parse_replacement_field_then_tail, Args, arg_id_end_pos, arg_index, manual_indexing_id>( - format_str); + fmt); } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { constexpr auto arg_index = get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); - if constexpr (arg_index != invalid_arg_index) { + if constexpr (arg_index >= 0) { constexpr auto next_id = ID != manual_indexing_id ? ID + 1 : manual_indexing_id; return parse_replacement_field_then_tail< decltype(get_type::value), Args, arg_id_end_pos, - arg_index, next_id>(format_str); - } else { - if constexpr (c == '}') { - return parse_tail( - runtime_named_field{arg_id_result.arg_id.val.name}, - format_str); - } else if constexpr (c == ':') { - return unknown_format(); // no type info for specs parsing - } + arg_index, next_id>(fmt); + } else if constexpr (c == '}') { + return parse_tail( + runtime_named_field{arg_id_result.arg_id.val.name}, + fmt); + } else if constexpr (c == ':') { + return unknown_format(); // no type info for specs parsing } } } } else if constexpr (str[POS] == '}') { if constexpr (POS + 1 == str.size()) FMT_THROW(format_error("unmatched '}' in format string")); - return parse_tail(make_text(str, POS, 1), format_str); + return parse_tail(make_text(str, POS, 1), fmt); } else { constexpr auto end = parse_text(str, POS + 1); if constexpr (end - POS > 1) { - return parse_tail(make_text(str, POS, end - POS), - format_str); + return parse_tail(make_text(str, POS, end - POS), fmt); } else { - return parse_tail(code_unit{str[POS]}, - format_str); + return parse_tail(code_unit{str[POS]}, fmt); } } } template ::value)> -constexpr auto compile(S format_str) { - constexpr auto str = basic_string_view(format_str); +constexpr auto compile(S fmt) { + constexpr auto str = basic_string_view(fmt); if constexpr (str.size() == 0) { return detail::make_text(str, 0, 0); } else { constexpr auto result = - detail::compile_format_string, 0, 0>( - format_str); + detail::compile_format_string, 0, 0>(fmt); return result; } } -#endif // __cpp_if_constexpr +#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) } // namespace detail -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT -#ifdef __cpp_if_constexpr +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) template format(const S&, constexpr auto compiled = detail::compile(S()); if constexpr (std::is_same, detail::unknown_format>()) { - return format(static_cast>(S()), - std::forward(args)...); + return fmt::format( + static_cast>(S()), + std::forward(args)...); } else { - return format(compiled, std::forward(args)...); + return fmt::format(compiled, std::forward(args)...); } } @@ -583,57 +473,57 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { constexpr auto compiled = detail::compile(S()); if constexpr (std::is_same, detail::unknown_format>()) { - return format_to(out, - static_cast>(S()), - std::forward(args)...); + return fmt::format_to( + out, static_cast>(S()), + std::forward(args)...); } else { - return format_to(out, compiled, std::forward(args)...); + return fmt::format_to(out, compiled, std::forward(args)...); } } #endif template ::value)> -format_to_n_result format_to_n(OutputIt out, size_t n, - const S& format_str, Args&&... args) { - auto it = format_to(detail::truncating_iterator(out, n), format_str, - std::forward(args)...); - return {it.base(), it.count()}; +auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + fmt::format_to(std::back_inserter(buf), fmt, std::forward(args)...); + return {buf.out(), buf.count()}; } template ::value)> -size_t formatted_size(const S& format_str, const Args&... args) { - return format_to(detail::counting_iterator(), format_str, args...).count(); +FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) + -> size_t { + return fmt::format_to(detail::counting_iterator(), fmt, args...).count(); } template ::value)> -void print(std::FILE* f, const S& format_str, const Args&... args) { +void print(std::FILE* f, const S& fmt, const Args&... args) { memory_buffer buffer; - format_to(std::back_inserter(buffer), format_str, args...); + fmt::format_to(std::back_inserter(buffer), fmt, args...); detail::print(f, {buffer.data(), buffer.size()}); } template ::value)> -void print(const S& format_str, const Args&... args) { - print(stdout, format_str, args...); +void print(const S& fmt, const Args&... args) { + print(stdout, fmt, args...); } -#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +#if FMT_USE_NONTYPE_TEMPLATE_ARGS inline namespace literals { -template -constexpr detail::udl_compiled_string< - remove_cvref_t, - sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str> -operator""_cf() { - return {}; +template constexpr auto operator""_cf() { + using char_t = remove_cvref_t; + return detail::udl_compiled_string(); } } // namespace literals #endif -FMT_MODULE_EXPORT_END +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COMPILE_H_ diff --git a/src/3rdparty/fmt/core.h b/src/3rdparty/fmt/core.h index 41748c24..8ca735f0 100644 --- a/src/3rdparty/fmt/core.h +++ b/src/3rdparty/fmt/core.h @@ -1,3162 +1,5 @@ -// Formatting library for C++ - the core API for char/UTF-8 -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. +// This file is only provided for compatibility and may be removed in future +// versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h +// otherwise. -#ifndef FMT_CORE_H_ -#define FMT_CORE_H_ - -#include // std::FILE -#include -#include -#include -#include -#include - -// The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 80001 - -#ifdef __clang__ -# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) -#else -# define FMT_CLANG_VERSION 0 -#endif - -#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) -# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -# define FMT_GCC_PRAGMA(arg) _Pragma(arg) -#else -# define FMT_GCC_VERSION 0 -# define FMT_GCC_PRAGMA(arg) -#endif - -#if defined(__INTEL_COMPILER) -# define FMT_ICC_VERSION __INTEL_COMPILER -#else -# define FMT_ICC_VERSION 0 -#endif - -#ifdef __NVCC__ -# define FMT_NVCC __NVCC__ -#else -# define FMT_NVCC 0 -#endif - -#ifdef _MSC_VER -# define FMT_MSC_VER _MSC_VER -# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) -#else -# define FMT_MSC_VER 0 -# define FMT_MSC_WARNING(...) -#endif - -#ifdef __has_feature -# define FMT_HAS_FEATURE(x) __has_feature(x) -#else -# define FMT_HAS_FEATURE(x) 0 -#endif - -#if defined(__has_include) && \ - (!defined(__INTELLISENSE__) || FMT_MSC_VER > 1900) && \ - (!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600) -# define FMT_HAS_INCLUDE(x) __has_include(x) -#else -# define FMT_HAS_INCLUDE(x) 0 -#endif - -#ifdef __has_cpp_attribute -# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) -#else -# define FMT_HAS_CPP_ATTRIBUTE(x) 0 -#endif - -#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ - (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) - -#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ - (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) - -// Check if relaxed C++14 constexpr is supported. -// GCC doesn't allow throw in constexpr until version 6 (bug 67371). -#ifndef FMT_USE_CONSTEXPR -# define FMT_USE_CONSTEXPR \ - (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1912 || \ - (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) && \ - !FMT_NVCC && !FMT_ICC_VERSION -#endif -#if FMT_USE_CONSTEXPR -# define FMT_CONSTEXPR constexpr -# define FMT_CONSTEXPR_DECL constexpr -#else -# define FMT_CONSTEXPR -# define FMT_CONSTEXPR_DECL -#endif - -#if ((__cplusplus >= 202002L) && \ - (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9)) || \ - (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002) -# define FMT_CONSTEXPR20 constexpr -#else -# define FMT_CONSTEXPR20 -#endif - -// Check if constexpr std::char_traits<>::compare,length is supported. -#if defined(__GLIBCXX__) -# if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \ - _GLIBCXX_RELEASE >= 7 // GCC 7+ libstdc++ has _GLIBCXX_RELEASE. -# define FMT_CONSTEXPR_CHAR_TRAITS constexpr -# endif -#elif defined(_LIBCPP_VERSION) && __cplusplus >= 201703L && \ - _LIBCPP_VERSION >= 4000 -# define FMT_CONSTEXPR_CHAR_TRAITS constexpr -#elif FMT_MSC_VER >= 1914 && _MSVC_LANG >= 201703L -# define FMT_CONSTEXPR_CHAR_TRAITS constexpr -#endif -#ifndef FMT_CONSTEXPR_CHAR_TRAITS -# define FMT_CONSTEXPR_CHAR_TRAITS -#endif - -// Check if exceptions are disabled. -#ifndef FMT_EXCEPTIONS -# if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \ - FMT_MSC_VER && !_HAS_EXCEPTIONS -# define FMT_EXCEPTIONS 0 -# else -# define FMT_EXCEPTIONS 1 -# endif -#endif - -// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). -#ifndef FMT_USE_NOEXCEPT -# define FMT_USE_NOEXCEPT 0 -#endif - -#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ - FMT_GCC_VERSION >= 408 || FMT_MSC_VER >= 1900 -# define FMT_DETECTED_NOEXCEPT noexcept -# define FMT_HAS_CXX11_NOEXCEPT 1 -#else -# define FMT_DETECTED_NOEXCEPT throw() -# define FMT_HAS_CXX11_NOEXCEPT 0 -#endif - -#ifndef FMT_NOEXCEPT -# if FMT_EXCEPTIONS || FMT_HAS_CXX11_NOEXCEPT -# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT -# else -# define FMT_NOEXCEPT -# endif -#endif - -// [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code -// warnings. -#if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VER && \ - !FMT_NVCC -# define FMT_NORETURN [[noreturn]] -#else -# define FMT_NORETURN -#endif - -#if __cplusplus == 201103L || __cplusplus == 201402L -# if defined(__INTEL_COMPILER) || defined(__PGI) -# define FMT_FALLTHROUGH -# elif defined(__clang__) -# define FMT_FALLTHROUGH [[clang::fallthrough]] -# elif FMT_GCC_VERSION >= 700 && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) -# define FMT_FALLTHROUGH [[gnu::fallthrough]] -# else -# define FMT_FALLTHROUGH -# endif -#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \ - (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define FMT_FALLTHROUGH [[fallthrough]] -#else -# define FMT_FALLTHROUGH -#endif - -#ifndef FMT_USE_FLOAT -# define FMT_USE_FLOAT 1 -#endif -#ifndef FMT_USE_DOUBLE -# define FMT_USE_DOUBLE 1 -#endif -#ifndef FMT_USE_LONG_DOUBLE -# define FMT_USE_LONG_DOUBLE 1 -#endif - -#ifndef FMT_INLINE -# if FMT_GCC_VERSION || FMT_CLANG_VERSION -# define FMT_INLINE inline __attribute__((always_inline)) -# else -# define FMT_INLINE inline -# endif -#endif - -#ifndef FMT_BEGIN_NAMESPACE -# define FMT_BEGIN_NAMESPACE \ - namespace fmt { \ - inline namespace v8 { -# define FMT_END_NAMESPACE \ - } \ - } -#endif - -#ifndef FMT_MODULE_EXPORT -# define FMT_MODULE_EXPORT -# define FMT_MODULE_EXPORT_BEGIN -# define FMT_MODULE_EXPORT_END -# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { -# define FMT_END_DETAIL_NAMESPACE } -#endif - -#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275) -# ifdef FMT_EXPORT -# define FMT_API __declspec(dllexport) -# elif defined(FMT_SHARED) -# define FMT_API __declspec(dllimport) -# endif -#else -# define FMT_CLASS_API -# if defined(FMT_EXPORT) || defined(FMT_SHARED) -# if defined(__GNUC__) || defined(__clang__) -# define FMT_API __attribute__((visibility("default"))) -# endif -# endif -#endif -#ifndef FMT_API -# define FMT_API -#endif - -// libc++ supports string_view in pre-c++17. -#if (FMT_HAS_INCLUDE() && \ - (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ - (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) -# include -# define FMT_USE_STRING_VIEW -#elif FMT_HAS_INCLUDE("experimental/string_view") && __cplusplus >= 201402L -# include -# define FMT_USE_EXPERIMENTAL_STRING_VIEW -#endif - -#ifndef FMT_UNICODE -# define FMT_UNICODE !FMT_MSC_VER -#endif - -#ifndef FMT_CONSTEVAL -# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ - __cplusplus > 201703L) || \ - (defined(__cpp_consteval) && \ - !FMT_MSC_VER) // consteval is broken in MSVC. -# define FMT_CONSTEVAL consteval -# define FMT_HAS_CONSTEVAL -# else -# define FMT_CONSTEVAL -# endif -#endif - -#ifndef FMT_USE_NONTYPE_TEMPLATE_PARAMETERS -# if defined(__cpp_nontype_template_args) && \ - ((FMT_GCC_VERSION >= 903 && __cplusplus >= 201709L) || \ - __cpp_nontype_template_args >= 201911L) -# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 1 -# else -# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 0 -# endif -#endif - -// Enable minimal optimizations for more compact code in debug mode. -FMT_GCC_PRAGMA("GCC push_options") -#ifndef __OPTIMIZE__ -FMT_GCC_PRAGMA("GCC optimize(\"Og\")") -#endif - -FMT_BEGIN_NAMESPACE -FMT_MODULE_EXPORT_BEGIN - -// Implementations of enable_if_t and other metafunctions for older systems. -template -using enable_if_t = typename std::enable_if::type; -template -using conditional_t = typename std::conditional::type; -template using bool_constant = std::integral_constant; -template -using remove_reference_t = typename std::remove_reference::type; -template -using remove_const_t = typename std::remove_const::type; -template -using remove_cvref_t = typename std::remove_cv>::type; -template struct type_identity { using type = T; }; -template using type_identity_t = typename type_identity::type; - -struct monostate { - constexpr monostate() {} -}; - -// An enable_if helper to be used in template parameters which results in much -// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed -// to workaround a bug in MSVC 2019 (see #1140 and #1186). -#ifdef FMT_DOC -# define FMT_ENABLE_IF(...) -#else -# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 -#endif - -FMT_BEGIN_DETAIL_NAMESPACE - -// Suppress "unused variable" warnings with the method described in -// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. -// (void)var does not work on many Intel compilers. -template FMT_CONSTEXPR void ignore_unused(const T&...) {} - -constexpr FMT_INLINE auto is_constant_evaluated() FMT_NOEXCEPT -> bool { -#ifdef __cpp_lib_is_constant_evaluated - return std::is_constant_evaluated(); -#else - return false; -#endif -} - -// A function to suppress "conditional expression is constant" warnings. -template constexpr FMT_INLINE auto const_check(T value) -> T { - return value; -} - -FMT_NORETURN FMT_API void assert_fail(const char* file, int line, - const char* message); - -#ifndef FMT_ASSERT -# ifdef NDEBUG -// FMT_ASSERT is not empty to avoid -Werror=empty-body. -# define FMT_ASSERT(condition, message) \ - ::fmt::detail::ignore_unused((condition), (message)) -# else -# define FMT_ASSERT(condition, message) \ - ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ - ? (void)0 \ - : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message))) -# endif -#endif - -#if defined(FMT_USE_STRING_VIEW) -template using std_string_view = std::basic_string_view; -#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) -template -using std_string_view = std::experimental::basic_string_view; -#else -template struct std_string_view {}; -#endif - -#ifdef FMT_USE_INT128 -// Do nothing. -#elif defined(__SIZEOF_INT128__) && !FMT_NVCC && \ - !(FMT_CLANG_VERSION && FMT_MSC_VER) -# define FMT_USE_INT128 1 -using int128_t = __int128_t; -using uint128_t = __uint128_t; -template inline auto convert_for_visit(T value) -> T { - return value; -} -#else -# define FMT_USE_INT128 0 -#endif -#if !FMT_USE_INT128 -enum class int128_t {}; -enum class uint128_t {}; -// Reduce template instantiations. -template inline auto convert_for_visit(T) -> monostate { - return {}; -} -#endif - -// Casts a nonnegative integer to unsigned. -template -FMT_CONSTEXPR auto to_unsigned(Int value) -> - typename std::make_unsigned::type { - FMT_ASSERT(value >= 0, "negative value"); - return static_cast::type>(value); -} - -FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5"; - -constexpr auto is_utf8() -> bool { - // Avoid buggy sign extensions in MSVC's constant evaluation mode. - // https://developercommunity.visualstudio.com/t/C-difference-in-behavior-for-unsigned/1233612 - using uchar = unsigned char; - return FMT_UNICODE || (sizeof(micro) == 3 && uchar(micro[0]) == 0xC2 && - uchar(micro[1]) == 0xB5); -} -FMT_END_DETAIL_NAMESPACE - -/** - An implementation of ``std::basic_string_view`` for pre-C++17. It provides a - subset of the API. ``fmt::basic_string_view`` is used for format strings even - if ``std::string_view`` is available to prevent issues when a library is - compiled with a different ``-std`` option than the client code (which is not - recommended). - */ -template class basic_string_view { - private: - const Char* data_; - size_t size_; - - public: - using value_type = Char; - using iterator = const Char*; - - constexpr basic_string_view() FMT_NOEXCEPT : data_(nullptr), size_(0) {} - - /** Constructs a string reference object from a C string and a size. */ - constexpr basic_string_view(const Char* s, size_t count) FMT_NOEXCEPT - : data_(s), - size_(count) {} - - /** - \rst - Constructs a string reference object from a C string computing - the size with ``std::char_traits::length``. - \endrst - */ - FMT_CONSTEXPR_CHAR_TRAITS - FMT_INLINE - basic_string_view(const Char* s) : data_(s) { - if (detail::const_check(std::is_same::value && - !detail::is_constant_evaluated())) - size_ = std::strlen(reinterpret_cast(s)); - else - size_ = std::char_traits::length(s); - } - - /** Constructs a string reference from a ``std::basic_string`` object. */ - template - FMT_CONSTEXPR basic_string_view( - const std::basic_string& s) FMT_NOEXCEPT - : data_(s.data()), - size_(s.size()) {} - - template >::value)> - FMT_CONSTEXPR basic_string_view(S s) FMT_NOEXCEPT : data_(s.data()), - size_(s.size()) {} - - /** Returns a pointer to the string data. */ - constexpr auto data() const -> const Char* { return data_; } - - /** Returns the string size. */ - constexpr auto size() const -> size_t { return size_; } - - constexpr auto begin() const -> iterator { return data_; } - constexpr auto end() const -> iterator { return data_ + size_; } - - constexpr auto operator[](size_t pos) const -> const Char& { - return data_[pos]; - } - - FMT_CONSTEXPR void remove_prefix(size_t n) { - data_ += n; - size_ -= n; - } - - // Lexicographically compare this string reference to other. - FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { - size_t str_size = size_ < other.size_ ? size_ : other.size_; - int result = std::char_traits::compare(data_, other.data_, str_size); - if (result == 0) - result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); - return result; - } - - FMT_CONSTEXPR_CHAR_TRAITS friend auto operator==(basic_string_view lhs, - basic_string_view rhs) - -> bool { - return lhs.compare(rhs) == 0; - } - friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) != 0; - } - friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) < 0; - } - friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) <= 0; - } - friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) > 0; - } - friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) >= 0; - } -}; - -using string_view = basic_string_view; - -/** Specifies if ``T`` is a character type. Can be specialized by users. */ -template struct is_char : std::false_type {}; -template <> struct is_char : std::true_type {}; - -// Returns a string view of `s`. -template ::value)> -FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view { - return s; -} -template -inline auto to_string_view(const std::basic_string& s) - -> basic_string_view { - return s; -} -template -constexpr auto to_string_view(basic_string_view s) - -> basic_string_view { - return s; -} -template >::value)> -inline auto to_string_view(detail::std_string_view s) - -> basic_string_view { - return s; -} - -// A base class for compile-time strings. It is defined in the fmt namespace to -// make formatting functions visible via ADL, e.g. format(FMT_STRING("{}"), 42). -struct compile_string {}; - -template -struct is_compile_string : std::is_base_of {}; - -template ::value)> -constexpr auto to_string_view(const S& s) - -> basic_string_view { - return basic_string_view(s); -} - -FMT_BEGIN_DETAIL_NAMESPACE - -void to_string_view(...); -using fmt::v8::to_string_view; - -// Specifies whether S is a string type convertible to fmt::basic_string_view. -// It should be a constexpr function but MSVC 2017 fails to compile it in -// enable_if and MSVC 2015 fails to compile it as an alias template. -template -struct is_string : std::is_class()))> { -}; - -template struct char_t_impl {}; -template struct char_t_impl::value>> { - using result = decltype(to_string_view(std::declval())); - using type = typename result::value_type; -}; - -// Reports a compile-time error if S is not a valid format string. -template ::value)> -FMT_INLINE void check_format_string(const S&) { -#ifdef FMT_ENFORCE_COMPILE_STRING - static_assert(is_compile_string::value, - "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " - "FMT_STRING."); -#endif -} -template ::value)> -void check_format_string(S); - -FMT_NORETURN FMT_API void throw_format_error(const char* message); - -struct error_handler { - constexpr error_handler() = default; - constexpr error_handler(const error_handler&) = default; - - // This function is intentionally not constexpr to give a compile-time error. - void on_error(const char* message) { throw_format_error(message); } -}; -FMT_END_DETAIL_NAMESPACE - -/** String's character type. */ -template using char_t = typename detail::char_t_impl::type; - -/** - \rst - Parsing context consisting of a format string range being parsed and an - argument counter for automatic indexing. - You can use the ``format_parse_context`` type alias for ``char`` instead. - \endrst - */ -template -class basic_format_parse_context : private ErrorHandler { - private: - basic_string_view format_str_; - int next_arg_id_; - - public: - using char_type = Char; - using iterator = typename basic_string_view::iterator; - - explicit constexpr basic_format_parse_context( - basic_string_view format_str, ErrorHandler eh = {}, - int next_arg_id = 0) - : ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {} - - /** - Returns an iterator to the beginning of the format string range being - parsed. - */ - constexpr auto begin() const FMT_NOEXCEPT -> iterator { - return format_str_.begin(); - } - - /** - Returns an iterator past the end of the format string range being parsed. - */ - constexpr auto end() const FMT_NOEXCEPT -> iterator { - return format_str_.end(); - } - - /** Advances the begin iterator to ``it``. */ - FMT_CONSTEXPR void advance_to(iterator it) { - format_str_.remove_prefix(detail::to_unsigned(it - begin())); - } - - /** - Reports an error if using the manual argument indexing; otherwise returns - the next argument index and switches to the automatic indexing. - */ - FMT_CONSTEXPR auto next_arg_id() -> int { - // Don't check if the argument id is valid to avoid overhead and because it - // will be checked during formatting anyway. - if (next_arg_id_ >= 0) return next_arg_id_++; - on_error("cannot switch from manual to automatic argument indexing"); - return 0; - } - - /** - Reports an error if using the automatic argument indexing; otherwise - switches to the manual indexing. - */ - FMT_CONSTEXPR void check_arg_id(int) { - if (next_arg_id_ > 0) - on_error("cannot switch from automatic to manual argument indexing"); - else - next_arg_id_ = -1; - } - - FMT_CONSTEXPR void check_arg_id(basic_string_view) {} - - FMT_CONSTEXPR void on_error(const char* message) { - ErrorHandler::on_error(message); - } - - constexpr auto error_handler() const -> ErrorHandler { return *this; } -}; - -using format_parse_context = basic_format_parse_context; - -template class basic_format_arg; -template class basic_format_args; -template class dynamic_format_arg_store; - -// A formatter for objects of type T. -template -struct formatter { - // A deleted default constructor indicates a disabled formatter. - formatter() = delete; -}; - -// Specifies if T has an enabled formatter specialization. A type can be -// formattable even if it doesn't have a formatter e.g. via a conversion. -template -using has_formatter = - std::is_constructible>; - -// Checks whether T is a container with contiguous storage. -template struct is_contiguous : std::false_type {}; -template -struct is_contiguous> : std::true_type {}; - -class appender; - -FMT_BEGIN_DETAIL_NAMESPACE - -template -constexpr auto has_const_formatter_impl(T*) - -> decltype(typename Context::template formatter_type().format( - std::declval(), std::declval()), - true) { - return true; -} -template -constexpr auto has_const_formatter_impl(...) -> bool { - return false; -} -template -constexpr auto has_const_formatter() -> bool { - return has_const_formatter_impl(static_cast(nullptr)); -} - -// Extracts a reference to the container from back_insert_iterator. -template -inline auto get_container(std::back_insert_iterator it) - -> Container& { - using bi_iterator = std::back_insert_iterator; - struct accessor : bi_iterator { - accessor(bi_iterator iter) : bi_iterator(iter) {} - using bi_iterator::container; - }; - return *accessor(it).container; -} - -template -FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out) - -> OutputIt { - while (begin != end) *out++ = static_cast(*begin++); - return out; -} - -template , U>::value&& is_char::value)> -FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out) -> U* { - if (is_constant_evaluated()) return copy_str(begin, end, out); - auto size = to_unsigned(end - begin); - memcpy(out, begin, size * sizeof(U)); - return out + size; -} - -/** - \rst - A contiguous memory buffer with an optional growing ability. It is an internal - class and shouldn't be used directly, only via `~fmt::basic_memory_buffer`. - \endrst - */ -template class buffer { - private: - T* ptr_; - size_t size_; - size_t capacity_; - - protected: - // Don't initialize ptr_ since it is not accessed to save a few cycles. - FMT_MSC_WARNING(suppress : 26495) - buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {} - - FMT_CONSTEXPR20 buffer(T* p = nullptr, size_t sz = 0, - size_t cap = 0) FMT_NOEXCEPT : ptr_(p), - size_(sz), - capacity_(cap) {} - - FMT_CONSTEXPR20 ~buffer() = default; - buffer(buffer&&) = default; - - /** Sets the buffer data and capacity. */ - FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT { - ptr_ = buf_data; - capacity_ = buf_capacity; - } - - /** Increases the buffer capacity to hold at least *capacity* elements. */ - virtual FMT_CONSTEXPR20 void grow(size_t capacity) = 0; - - public: - using value_type = T; - using const_reference = const T&; - - buffer(const buffer&) = delete; - void operator=(const buffer&) = delete; - - auto begin() FMT_NOEXCEPT -> T* { return ptr_; } - auto end() FMT_NOEXCEPT -> T* { return ptr_ + size_; } - - auto begin() const FMT_NOEXCEPT -> const T* { return ptr_; } - auto end() const FMT_NOEXCEPT -> const T* { return ptr_ + size_; } - - /** Returns the size of this buffer. */ - constexpr auto size() const FMT_NOEXCEPT -> size_t { return size_; } - - /** Returns the capacity of this buffer. */ - constexpr auto capacity() const FMT_NOEXCEPT -> size_t { return capacity_; } - - /** Returns a pointer to the buffer data. */ - FMT_CONSTEXPR auto data() FMT_NOEXCEPT -> T* { return ptr_; } - - /** Returns a pointer to the buffer data. */ - FMT_CONSTEXPR auto data() const FMT_NOEXCEPT -> const T* { return ptr_; } - - /** Clears this buffer. */ - void clear() { size_ = 0; } - - // Tries resizing the buffer to contain *count* elements. If T is a POD type - // the new elements may not be initialized. - FMT_CONSTEXPR20 void try_resize(size_t count) { - try_reserve(count); - size_ = count <= capacity_ ? count : capacity_; - } - - // Tries increasing the buffer capacity to *new_capacity*. It can increase the - // capacity by a smaller amount than requested but guarantees there is space - // for at least one additional element either by increasing the capacity or by - // flushing the buffer if it is full. - FMT_CONSTEXPR20 void try_reserve(size_t new_capacity) { - if (new_capacity > capacity_) grow(new_capacity); - } - - FMT_CONSTEXPR20 void push_back(const T& value) { - try_reserve(size_ + 1); - ptr_[size_++] = value; - } - - /** Appends data to the end of the buffer. */ - template void append(const U* begin, const U* end); - - template FMT_CONSTEXPR auto operator[](I index) -> T& { - return ptr_[index]; - } - template - FMT_CONSTEXPR auto operator[](I index) const -> const T& { - return ptr_[index]; - } -}; - -struct buffer_traits { - explicit buffer_traits(size_t) {} - auto count() const -> size_t { return 0; } - auto limit(size_t size) -> size_t { return size; } -}; - -class fixed_buffer_traits { - private: - size_t count_ = 0; - size_t limit_; - - public: - explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} - auto count() const -> size_t { return count_; } - auto limit(size_t size) -> size_t { - size_t n = limit_ > count_ ? limit_ - count_ : 0; - count_ += size; - return size < n ? size : n; - } -}; - -// A buffer that writes to an output iterator when flushed. -template -class iterator_buffer final : public Traits, public buffer { - private: - OutputIt out_; - enum { buffer_size = 256 }; - T data_[buffer_size]; - - protected: - void grow(size_t) override { - if (this->size() == buffer_size) flush(); - } - - void flush() { - auto size = this->size(); - this->clear(); - out_ = copy_str(data_, data_ + this->limit(size), out_); - } - - public: - explicit iterator_buffer(OutputIt out, size_t n = buffer_size) - : Traits(n), buffer(data_, 0, buffer_size), out_(out) {} - iterator_buffer(iterator_buffer&& other) - : Traits(other), buffer(data_, 0, buffer_size), out_(other.out_) {} - ~iterator_buffer() { flush(); } - - auto out() -> OutputIt { - flush(); - return out_; - } - auto count() const -> size_t { return Traits::count() + this->size(); } -}; - -template -class iterator_buffer final : public fixed_buffer_traits, public buffer { - private: - T* out_; - enum { buffer_size = 256 }; - T data_[buffer_size]; - - protected: - void grow(size_t) override { - if (this->size() == this->capacity()) flush(); - } - - void flush() { - size_t n = this->limit(this->size()); - if (this->data() == out_) { - out_ += n; - this->set(data_, buffer_size); - } - this->clear(); - } - - public: - explicit iterator_buffer(T* out, size_t n = buffer_size) - : fixed_buffer_traits(n), buffer(out, 0, n), out_(out) {} - iterator_buffer(iterator_buffer&& other) - : fixed_buffer_traits(other), buffer(std::move(other)), out_(other.out_) { - if (this->data() != out_) { - this->set(data_, buffer_size); - this->clear(); - } - } - ~iterator_buffer() { flush(); } - - auto out() -> T* { - flush(); - return out_; - } - auto count() const -> size_t { return fixed_buffer_traits::count() + this->size(); } -}; - -template class iterator_buffer final : public buffer { - protected: - void grow(size_t) override {} - - public: - explicit iterator_buffer(T* out, size_t = 0) : buffer(out, 0, ~size_t()) {} - - auto out() -> T* { return &*this->end(); } -}; - -// A buffer that writes to a container with the contiguous storage. -template -class iterator_buffer, - enable_if_t::value, - typename Container::value_type>> - final : public buffer { - private: - Container& container_; - - protected: - void grow(size_t capacity) override { - container_.resize(capacity); - this->set(&container_[0], capacity); - } - - public: - explicit iterator_buffer(Container& c) - : buffer(c.size()), container_(c) {} - explicit iterator_buffer(std::back_insert_iterator out, size_t = 0) - : iterator_buffer(get_container(out)) {} - auto out() -> std::back_insert_iterator { - return std::back_inserter(container_); - } -}; - -// A buffer that counts the number of code units written discarding the output. -template class counting_buffer final : public buffer { - private: - enum { buffer_size = 256 }; - T data_[buffer_size]; - size_t count_ = 0; - - protected: - void grow(size_t) override { - if (this->size() != buffer_size) return; - count_ += this->size(); - this->clear(); - } - - public: - counting_buffer() : buffer(data_, 0, buffer_size) {} - - auto count() -> size_t { return count_ + this->size(); } -}; - -template -using buffer_appender = conditional_t::value, appender, - std::back_insert_iterator>>; - -// Maps an output iterator to a buffer. -template -auto get_buffer(OutputIt out) -> iterator_buffer { - return iterator_buffer(out); -} - -template -auto get_iterator(Buffer& buf) -> decltype(buf.out()) { - return buf.out(); -} -template auto get_iterator(buffer& buf) -> buffer_appender { - return buffer_appender(buf); -} - -template -struct fallback_formatter { - fallback_formatter() = delete; -}; - -// Specifies if T has an enabled fallback_formatter specialization. -template -using has_fallback_formatter = - std::is_constructible>; - -struct view {}; - -template struct named_arg : view { - const Char* name; - const T& value; - named_arg(const Char* n, const T& v) : name(n), value(v) {} -}; - -template struct named_arg_info { - const Char* name; - int id; -}; - -template -struct arg_data { - // args_[0].named_args points to named_args_ to avoid bloating format_args. - // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. - T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)]; - named_arg_info named_args_[NUM_NAMED_ARGS]; - - template - arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {} - arg_data(const arg_data& other) = delete; - auto args() const -> const T* { return args_ + 1; } - auto named_args() -> named_arg_info* { return named_args_; } -}; - -template -struct arg_data { - // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. - T args_[NUM_ARGS != 0 ? NUM_ARGS : +1]; - - template - FMT_CONSTEXPR FMT_INLINE arg_data(const U&... init) : args_{init...} {} - FMT_CONSTEXPR FMT_INLINE auto args() const -> const T* { return args_; } - FMT_CONSTEXPR FMT_INLINE auto named_args() -> std::nullptr_t { - return nullptr; - } -}; - -template -inline void init_named_args(named_arg_info*, int, int) {} - -template struct is_named_arg : std::false_type {}; -template struct is_statically_named_arg : std::false_type {}; - -template -struct is_named_arg> : std::true_type {}; - -template ::value)> -void init_named_args(named_arg_info* named_args, int arg_count, - int named_arg_count, const T&, const Tail&... args) { - init_named_args(named_args, arg_count + 1, named_arg_count, args...); -} - -template ::value)> -void init_named_args(named_arg_info* named_args, int arg_count, - int named_arg_count, const T& arg, const Tail&... args) { - named_args[named_arg_count++] = {arg.name, arg_count}; - init_named_args(named_args, arg_count + 1, named_arg_count, args...); -} - -template -FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int, - const Args&...) {} - -template constexpr auto count() -> size_t { return B ? 1 : 0; } -template constexpr auto count() -> size_t { - return (B1 ? 1 : 0) + count(); -} - -template constexpr auto count_named_args() -> size_t { - return count::value...>(); -} - -enum class type { - none_type, - // Integer types should go first, - int_type, - uint_type, - long_long_type, - ulong_long_type, - int128_type, - uint128_type, - bool_type, - char_type, - last_integer_type = char_type, - // followed by floating-point types. - float_type, - double_type, - long_double_type, - last_numeric_type = long_double_type, - cstring_type, - string_type, - pointer_type, - custom_type -}; - -// Maps core type T to the corresponding type enum constant. -template -struct type_constant : std::integral_constant {}; - -#define FMT_TYPE_CONSTANT(Type, constant) \ - template \ - struct type_constant \ - : std::integral_constant {} - -FMT_TYPE_CONSTANT(int, int_type); -FMT_TYPE_CONSTANT(unsigned, uint_type); -FMT_TYPE_CONSTANT(long long, long_long_type); -FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); -FMT_TYPE_CONSTANT(int128_t, int128_type); -FMT_TYPE_CONSTANT(uint128_t, uint128_type); -FMT_TYPE_CONSTANT(bool, bool_type); -FMT_TYPE_CONSTANT(Char, char_type); -FMT_TYPE_CONSTANT(float, float_type); -FMT_TYPE_CONSTANT(double, double_type); -FMT_TYPE_CONSTANT(long double, long_double_type); -FMT_TYPE_CONSTANT(const Char*, cstring_type); -FMT_TYPE_CONSTANT(basic_string_view, string_type); -FMT_TYPE_CONSTANT(const void*, pointer_type); - -constexpr bool is_integral_type(type t) { - return t > type::none_type && t <= type::last_integer_type; -} - -constexpr bool is_arithmetic_type(type t) { - return t > type::none_type && t <= type::last_numeric_type; -} - -struct unformattable {}; -struct unformattable_char : unformattable {}; -struct unformattable_const : unformattable {}; -struct unformattable_pointer : unformattable {}; - -template struct string_value { - const Char* data; - size_t size; -}; - -template struct named_arg_value { - const named_arg_info* data; - size_t size; -}; - -template struct custom_value { - using parse_context = typename Context::parse_context_type; - void* value; - void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); -}; - -// A formatting argument value. -template class value { - public: - using char_type = typename Context::char_type; - - union { - monostate no_value; - int int_value; - unsigned uint_value; - long long long_long_value; - unsigned long long ulong_long_value; - int128_t int128_value; - uint128_t uint128_value; - bool bool_value; - char_type char_value; - float float_value; - double double_value; - long double long_double_value; - const void* pointer; - string_value string; - custom_value custom; - named_arg_value named_args; - }; - - constexpr FMT_INLINE value() : no_value() {} - constexpr FMT_INLINE value(int val) : int_value(val) {} - constexpr FMT_INLINE value(unsigned val) : uint_value(val) {} - constexpr FMT_INLINE value(long long val) : long_long_value(val) {} - constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} - FMT_INLINE value(int128_t val) : int128_value(val) {} - FMT_INLINE value(uint128_t val) : uint128_value(val) {} - constexpr FMT_INLINE value(float val) : float_value(val) {} - constexpr FMT_INLINE value(double val) : double_value(val) {} - FMT_INLINE value(long double val) : long_double_value(val) {} - constexpr FMT_INLINE value(bool val) : bool_value(val) {} - constexpr FMT_INLINE value(char_type val) : char_value(val) {} - FMT_CONSTEXPR FMT_INLINE value(const char_type* val) { - string.data = val; - if (is_constant_evaluated()) string.size = {}; - } - FMT_CONSTEXPR FMT_INLINE value(basic_string_view val) { - string.data = val.data(); - string.size = val.size(); - } - FMT_INLINE value(const void* val) : pointer(val) {} - FMT_INLINE value(const named_arg_info* args, size_t size) - : named_args{args, size} {} - - template FMT_CONSTEXPR FMT_INLINE value(T& val) { - using value_type = remove_cvref_t; - custom.value = const_cast(&val); - // Get the formatter type through the context to allow different contexts - // have different extension points, e.g. `formatter` for `format` and - // `printf_formatter` for `printf`. - custom.format = format_custom_arg< - value_type, - conditional_t::value, - typename Context::template formatter_type, - fallback_formatter>>; - } - value(unformattable); - value(unformattable_char); - value(unformattable_const); - value(unformattable_pointer); - - private: - // Formats an argument of a custom type, such as a user-defined class. - template - static void format_custom_arg(void* arg, - typename Context::parse_context_type& parse_ctx, - Context& ctx) { - auto f = Formatter(); - parse_ctx.advance_to(f.parse(parse_ctx)); - using qualified_type = - conditional_t(), const T, T>; - ctx.advance_to(f.format(*static_cast(arg), ctx)); - } -}; - -template -FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg; - -// To minimize the number of types we need to deal with, long is translated -// either to int or to long long depending on its size. -enum { long_short = sizeof(long) == sizeof(int) }; -using long_type = conditional_t; -using ulong_type = conditional_t; - -// Maps formatting arguments to core types. -// arg_mapper reports errors by returning unformattable instead of using -// static_assert because it's used in the is_formattable trait. -template struct arg_mapper { - using char_type = typename Context::char_type; - - FMT_CONSTEXPR FMT_INLINE auto map(signed char val) -> int { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned char val) -> unsigned { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(short val) -> int { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned short val) -> unsigned { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(int val) -> int { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned val) -> unsigned { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(long val) -> long_type { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned long val) -> ulong_type { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(long long val) -> long long { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned long long val) - -> unsigned long long { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(int128_t val) -> int128_t { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(uint128_t val) -> uint128_t { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(bool val) -> bool { return val; } - - template ::value || - std::is_same::value)> - FMT_CONSTEXPR FMT_INLINE auto map(T val) -> char_type { - return val; - } - template ::value && - !std::is_same::value)> - FMT_CONSTEXPR FMT_INLINE auto map(T) -> unformattable_char { - return {}; - } - - FMT_CONSTEXPR FMT_INLINE auto map(float val) -> float { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(double val) -> double { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(long double val) -> long double { - return val; - } - - FMT_CONSTEXPR FMT_INLINE auto map(char_type* val) -> const char_type* { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(const char_type* val) -> const char_type* { - return val; - } - template ::value && !std::is_pointer::value && - std::is_same>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> basic_string_view { - return to_string_view(val); - } - template ::value && !std::is_pointer::value && - !std::is_same>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T&) -> unformattable_char { - return {}; - } - template , T>::value && - !is_string::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> basic_string_view { - return basic_string_view(val); - } - template < - typename T, - FMT_ENABLE_IF( - std::is_constructible, T>::value && - !std::is_constructible, T>::value && - !is_string::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> basic_string_view { - return std_string_view(val); - } - FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val) - -> decltype(this->map("")) { - return map(reinterpret_cast(val)); - } - FMT_CONSTEXPR FMT_INLINE auto map(const unsigned char* val) - -> decltype(this->map("")) { - return map(reinterpret_cast(val)); - } - FMT_CONSTEXPR FMT_INLINE auto map(signed char* val) - -> decltype(this->map("")) { - return map(reinterpret_cast(val)); - } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val) - -> decltype(this->map("")) { - return map(reinterpret_cast(val)); - } - - FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(std::nullptr_t val) -> const void* { - return val; - } - - // We use SFINAE instead of a const T* parameter to avoid conflicting with - // the C array overload. - template ::value)> - FMT_CONSTEXPR auto map(T) -> unformattable_pointer { - return {}; - } - - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] { - return values; - } - - template ::value && - !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> decltype(std::declval().map( - static_cast::type>(val))) { - return map(static_cast::type>(val)); - } - - template > - struct formattable - : bool_constant() || - !std::is_const>::value || - has_fallback_formatter::value> {}; - -#if FMT_MSC_VER != 0 && FMT_MSC_VER < 1910 - // Workaround a bug in MSVC. - template FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { - return val; - } -#else - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { - return val; - } - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable_const { - return {}; - } -#endif - - template , - FMT_ENABLE_IF(!is_string::value && !is_char::value && - !std::is_array::value && - (has_formatter::value || - has_fallback_formatter::value))> - FMT_CONSTEXPR FMT_INLINE auto map(T&& val) - -> decltype(this->do_map(std::forward(val))) { - return do_map(std::forward(val)); - } - - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) - -> decltype(std::declval().map(named_arg.value)) { - return map(named_arg.value); - } - - auto map(...) -> unformattable { return {}; } -}; - -// A type constant after applying arg_mapper. -template -using mapped_type_constant = - type_constant().map(std::declval())), - typename Context::char_type>; - -enum { packed_arg_bits = 4 }; -// Maximum number of arguments with packed types. -enum { max_packed_args = 62 / packed_arg_bits }; -enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; -enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; - -FMT_END_DETAIL_NAMESPACE - -// An output iterator that appends to a buffer. -// It is used to reduce symbol sizes for the common case. -class appender : public std::back_insert_iterator> { - using base = std::back_insert_iterator>; - - template - friend auto get_buffer(appender out) -> detail::buffer& { - return detail::get_container(out); - } - - public: - using std::back_insert_iterator>::back_insert_iterator; - appender(base it) : base(it) {} - using _Unchecked_type = appender; // Mark iterator as checked. - - auto operator++() -> appender& { - base::operator++(); - return *this; - } - - auto operator++(int) -> appender { - auto tmp = *this; - ++*this; - return tmp; - } -}; - -// A formatting argument. It is a trivially copyable/constructible type to -// allow storage in basic_memory_buffer. -template class basic_format_arg { - private: - detail::value value_; - detail::type type_; - - template - friend FMT_CONSTEXPR auto detail::make_arg(const T& value) - -> basic_format_arg; - - template - friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, - const basic_format_arg& arg) - -> decltype(vis(0)); - - friend class basic_format_args; - friend class dynamic_format_arg_store; - - using char_type = typename Context::char_type; - - template - friend struct detail::arg_data; - - basic_format_arg(const detail::named_arg_info* args, size_t size) - : value_(args, size) {} - - public: - class handle { - public: - explicit handle(detail::custom_value custom) : custom_(custom) {} - - void format(typename Context::parse_context_type& parse_ctx, - Context& ctx) const { - custom_.format(custom_.value, parse_ctx, ctx); - } - - private: - detail::custom_value custom_; - }; - - constexpr basic_format_arg() : type_(detail::type::none_type) {} - - constexpr explicit operator bool() const FMT_NOEXCEPT { - return type_ != detail::type::none_type; - } - - auto type() const -> detail::type { return type_; } - - auto is_integral() const -> bool { return detail::is_integral_type(type_); } - auto is_arithmetic() const -> bool { - return detail::is_arithmetic_type(type_); - } -}; - -/** - \rst - Visits an argument dispatching to the appropriate visit method based on - the argument type. For example, if the argument type is ``double`` then - ``vis(value)`` will be called with the value of type ``double``. - \endrst - */ -template -FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( - Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { - switch (arg.type_) { - case detail::type::none_type: - break; - case detail::type::int_type: - return vis(arg.value_.int_value); - case detail::type::uint_type: - return vis(arg.value_.uint_value); - case detail::type::long_long_type: - return vis(arg.value_.long_long_value); - case detail::type::ulong_long_type: - return vis(arg.value_.ulong_long_value); - case detail::type::int128_type: - return vis(detail::convert_for_visit(arg.value_.int128_value)); - case detail::type::uint128_type: - return vis(detail::convert_for_visit(arg.value_.uint128_value)); - case detail::type::bool_type: - return vis(arg.value_.bool_value); - case detail::type::char_type: - return vis(arg.value_.char_value); - case detail::type::float_type: - return vis(arg.value_.float_value); - case detail::type::double_type: - return vis(arg.value_.double_value); - case detail::type::long_double_type: - return vis(arg.value_.long_double_value); - case detail::type::cstring_type: - return vis(arg.value_.string.data); - case detail::type::string_type: - using sv = basic_string_view; - return vis(sv(arg.value_.string.data, arg.value_.string.size)); - case detail::type::pointer_type: - return vis(arg.value_.pointer); - case detail::type::custom_type: - return vis(typename basic_format_arg::handle(arg.value_.custom)); - } - return vis(monostate()); -} - -FMT_BEGIN_DETAIL_NAMESPACE - -template -auto copy_str(InputIt begin, InputIt end, appender out) -> appender { - get_container(out).append(begin, end); - return out; -} - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 -// A workaround for gcc 4.8 to make void_t work in a SFINAE context. -template struct void_t_impl { using type = void; }; -template -using void_t = typename detail::void_t_impl::type; -#else -template using void_t = void; -#endif - -template -struct is_output_iterator : std::false_type {}; - -template -struct is_output_iterator< - It, T, - void_t::iterator_category, - decltype(*std::declval() = std::declval())>> - : std::true_type {}; - -template -struct is_back_insert_iterator : std::false_type {}; -template -struct is_back_insert_iterator> - : std::true_type {}; - -template -struct is_contiguous_back_insert_iterator : std::false_type {}; -template -struct is_contiguous_back_insert_iterator> - : is_contiguous {}; -template <> -struct is_contiguous_back_insert_iterator : std::true_type {}; - -// A type-erased reference to an std::locale to avoid heavy include. -class locale_ref { - private: - const void* locale_; // A type-erased pointer to std::locale. - - public: - constexpr locale_ref() : locale_(nullptr) {} - template explicit locale_ref(const Locale& loc); - - explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; } - - template auto get() const -> Locale; -}; - -template constexpr auto encode_types() -> unsigned long long { - return 0; -} - -template -constexpr auto encode_types() -> unsigned long long { - return static_cast(mapped_type_constant::value) | - (encode_types() << packed_arg_bits); -} - -template -FMT_CONSTEXPR auto make_arg(const T& value) -> basic_format_arg { - basic_format_arg arg; - arg.type_ = mapped_type_constant::value; - arg.value_ = arg_mapper().map(value); - return arg; -} - -// The type template parameter is there to avoid an ODR violation when using -// a fallback formatter in one translation unit and an implicit conversion in -// another (not recommended). -template -FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value { - const auto& arg = arg_mapper().map(std::forward(val)); - - constexpr bool formattable_char = - !std::is_same::value; - static_assert(formattable_char, "Mixing character types is disallowed."); - - constexpr bool formattable_const = - !std::is_same::value; - static_assert(formattable_const, "Cannot format a const argument."); - - // Formatting of arbitrary pointers is disallowed. If you want to output - // a pointer cast it to "void *" or "const void *". In particular, this - // forbids formatting of "[const] volatile char *" which is printed as bool - // by iostreams. - constexpr bool formattable_pointer = - !std::is_same::value; - static_assert(formattable_pointer, - "Formatting of non-void pointers is disallowed."); - - constexpr bool formattable = - !std::is_same::value; - static_assert( - formattable, - "Cannot format an argument. To make type T formattable provide a " - "formatter specialization: https://fmt.dev/latest/api.html#udt"); - return {arg}; -} - -template -inline auto make_arg(const T& value) -> basic_format_arg { - return make_arg(value); -} -FMT_END_DETAIL_NAMESPACE - -// Formatting context. -template class basic_format_context { - public: - /** The character type for the output. */ - using char_type = Char; - - private: - OutputIt out_; - basic_format_args args_; - detail::locale_ref loc_; - - public: - using iterator = OutputIt; - using format_arg = basic_format_arg; - using parse_context_type = basic_format_parse_context; - template using formatter_type = formatter; - - basic_format_context(basic_format_context&&) = default; - basic_format_context(const basic_format_context&) = delete; - void operator=(const basic_format_context&) = delete; - /** - Constructs a ``basic_format_context`` object. References to the arguments are - stored in the object so make sure they have appropriate lifetimes. - */ - constexpr basic_format_context( - OutputIt out, basic_format_args ctx_args, - detail::locale_ref loc = detail::locale_ref()) - : out_(out), args_(ctx_args), loc_(loc) {} - - constexpr auto arg(int id) const -> format_arg { return args_.get(id); } - FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { - return args_.get(name); - } - FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { - return args_.get_id(name); - } - auto args() const -> const basic_format_args& { - return args_; - } - - FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } - void on_error(const char* message) { error_handler().on_error(message); } - - // Returns an iterator to the beginning of the output range. - FMT_CONSTEXPR auto out() -> iterator { return out_; } - - // Advances the begin iterator to ``it``. - void advance_to(iterator it) { - if (!detail::is_back_insert_iterator()) out_ = it; - } - - FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } -}; - -template -using buffer_context = - basic_format_context, Char>; -using format_context = buffer_context; - -// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164. -#define FMT_BUFFER_CONTEXT(Char) \ - basic_format_context, Char> - -template -using is_formattable = bool_constant< - !std::is_base_of>().map( - std::declval()))>::value && - !detail::has_fallback_formatter::value>; - -/** - \rst - An array of references to arguments. It can be implicitly converted into - `~fmt::basic_format_args` for passing into type-erased formatting functions - such as `~fmt::vformat`. - \endrst - */ -template -class format_arg_store -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args -#endif -{ - private: - static const size_t num_args = sizeof...(Args); - static const size_t num_named_args = detail::count_named_args(); - static const bool is_packed = num_args <= detail::max_packed_args; - - using value_type = conditional_t, - basic_format_arg>; - - detail::arg_data - data_; - - friend class basic_format_args; - - static constexpr unsigned long long desc = - (is_packed ? detail::encode_types() - : detail::is_unpacked_bit | num_args) | - (num_named_args != 0 - ? static_cast(detail::has_named_args_bit) - : 0); - - public: - template - FMT_CONSTEXPR FMT_INLINE format_arg_store(T&&... args) - : -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - basic_format_args(*this), -#endif - data_{detail::make_arg< - is_packed, Context, - detail::mapped_type_constant, Context>::value>( - std::forward(args))...} { - detail::init_named_args(data_.named_args(), 0, 0, args...); - } -}; - -/** - \rst - Constructs a `~fmt::format_arg_store` object that contains references to - arguments and can be implicitly converted to `~fmt::format_args`. `Context` - can be omitted in which case it defaults to `~fmt::context`. - See `~fmt::arg` for lifetime considerations. - \endrst - */ -template -constexpr auto make_format_args(Args&&... args) - -> format_arg_store...> { - return {std::forward(args)...}; -} - -/** - \rst - Returns a named argument to be used in a formatting function. - It should only be used in a call to a formatting function or - `dynamic_format_arg_store::push_back`. - - **Example**:: - - fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); - \endrst - */ -template -inline auto arg(const Char* name, const T& arg) -> detail::named_arg { - static_assert(!detail::is_named_arg(), "nested named arguments"); - return {name, arg}; -} - -/** - \rst - A view of a collection of formatting arguments. To avoid lifetime issues it - should only be used as a parameter type in type-erased functions such as - ``vformat``:: - - void vlog(string_view format_str, format_args args); // OK - format_args args = make_format_args(42); // Error: dangling reference - \endrst - */ -template class basic_format_args { - public: - using size_type = int; - using format_arg = basic_format_arg; - - private: - // A descriptor that contains information about formatting arguments. - // If the number of arguments is less or equal to max_packed_args then - // argument types are passed in the descriptor. This reduces binary code size - // per formatting function call. - unsigned long long desc_; - union { - // If is_packed() returns true then argument values are stored in values_; - // otherwise they are stored in args_. This is done to improve cache - // locality and reduce compiled code size since storing larger objects - // may require more code (at least on x86-64) even if the same amount of - // data is actually copied to stack. It saves ~10% on the bloat test. - const detail::value* values_; - const format_arg* args_; - }; - - constexpr auto is_packed() const -> bool { - return (desc_ & detail::is_unpacked_bit) == 0; - } - auto has_named_args() const -> bool { - return (desc_ & detail::has_named_args_bit) != 0; - } - - FMT_CONSTEXPR auto type(int index) const -> detail::type { - int shift = index * detail::packed_arg_bits; - unsigned int mask = (1 << detail::packed_arg_bits) - 1; - return static_cast((desc_ >> shift) & mask); - } - - constexpr FMT_INLINE basic_format_args(unsigned long long desc, - const detail::value* values) - : desc_(desc), values_(values) {} - constexpr basic_format_args(unsigned long long desc, const format_arg* args) - : desc_(desc), args_(args) {} - - public: - constexpr basic_format_args() : desc_(0), args_(nullptr) {} - - /** - \rst - Constructs a `basic_format_args` object from `~fmt::format_arg_store`. - \endrst - */ - template - constexpr FMT_INLINE basic_format_args( - const format_arg_store& store) - : basic_format_args(format_arg_store::desc, - store.data_.args()) {} - - /** - \rst - Constructs a `basic_format_args` object from - `~fmt::dynamic_format_arg_store`. - \endrst - */ - constexpr FMT_INLINE basic_format_args( - const dynamic_format_arg_store& store) - : basic_format_args(store.get_types(), store.data()) {} - - /** - \rst - Constructs a `basic_format_args` object from a dynamic set of arguments. - \endrst - */ - constexpr basic_format_args(const format_arg* args, int count) - : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), - args) {} - - /** Returns the argument with the specified id. */ - FMT_CONSTEXPR auto get(int id) const -> format_arg { - format_arg arg; - if (!is_packed()) { - if (id < max_size()) arg = args_[id]; - return arg; - } - if (id >= detail::max_packed_args) return arg; - arg.type_ = type(id); - if (arg.type_ == detail::type::none_type) return arg; - arg.value_ = values_[id]; - return arg; - } - - template - auto get(basic_string_view name) const -> format_arg { - int id = get_id(name); - return id >= 0 ? get(id) : format_arg(); - } - - template - auto get_id(basic_string_view name) const -> int { - if (!has_named_args()) return -1; - const auto& named_args = - (is_packed() ? values_[-1] : args_[-1].value_).named_args; - for (size_t i = 0; i < named_args.size; ++i) { - if (named_args.data[i].name == name) return named_args.data[i].id; - } - return -1; - } - - auto max_size() const -> int { - unsigned long long max_packed = detail::max_packed_args; - return static_cast(is_packed() ? max_packed - : desc_ & ~detail::is_unpacked_bit); - } -}; - -/** An alias to ``basic_format_args``. */ -// A separate type would result in shorter symbols but break ABI compatibility -// between clang and gcc on ARM (#1919). -using format_args = basic_format_args; - -// We cannot use enum classes as bit fields because of a gcc bug -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414. -namespace align { -enum type { none, left, right, center, numeric }; -} -using align_t = align::type; -namespace sign { -enum type { none, minus, plus, space }; -} -using sign_t = sign::type; - -FMT_BEGIN_DETAIL_NAMESPACE - -// Workaround an array initialization issue in gcc 4.8. -template struct fill_t { - private: - enum { max_size = 4 }; - Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)}; - unsigned char size_ = 1; - - public: - FMT_CONSTEXPR void operator=(basic_string_view s) { - auto size = s.size(); - if (size > max_size) return throw_format_error("invalid fill"); - for (size_t i = 0; i < size; ++i) data_[i] = s[i]; - size_ = static_cast(size); - } - - constexpr auto size() const -> size_t { return size_; } - constexpr auto data() const -> const Char* { return data_; } - - FMT_CONSTEXPR auto operator[](size_t index) -> Char& { return data_[index]; } - FMT_CONSTEXPR auto operator[](size_t index) const -> const Char& { - return data_[index]; - } -}; -FMT_END_DETAIL_NAMESPACE - -enum class presentation_type : unsigned char { - none, - // Integer types should go first, - dec, // 'd' - oct, // 'o' - hex_lower, // 'x' - hex_upper, // 'X' - bin_lower, // 'b' - bin_upper, // 'B' - hexfloat_lower, // 'a' - hexfloat_upper, // 'A' - exp_lower, // 'e' - exp_upper, // 'E' - fixed_lower, // 'f' - fixed_upper, // 'F' - general_lower, // 'g' - general_upper, // 'G' - chr, // 'c' - string, // 's' - pointer // 'p' -}; - -// Format specifiers for built-in and string types. -template struct basic_format_specs { - int width; - int precision; - presentation_type type; - align_t align : 4; - sign_t sign : 3; - bool alt : 1; // Alternate form ('#'). - bool localized : 1; - detail::fill_t fill; - - constexpr basic_format_specs() - : width(0), - precision(-1), - type(presentation_type::none), - align(align::none), - sign(sign::none), - alt(false), - localized(false) {} -}; - -using format_specs = basic_format_specs; - -FMT_BEGIN_DETAIL_NAMESPACE - -enum class arg_id_kind { none, index, name }; - -// An argument reference. -template struct arg_ref { - FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} - - FMT_CONSTEXPR explicit arg_ref(int index) - : kind(arg_id_kind::index), val(index) {} - FMT_CONSTEXPR explicit arg_ref(basic_string_view name) - : kind(arg_id_kind::name), val(name) {} - - FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& { - kind = arg_id_kind::index; - val.index = idx; - return *this; - } - - arg_id_kind kind; - union value { - FMT_CONSTEXPR value(int id = 0) : index{id} {} - FMT_CONSTEXPR value(basic_string_view n) : name(n) {} - - int index; - basic_string_view name; - } val; -}; - -// Format specifiers with width and precision resolved at formatting rather -// than parsing time to allow re-using the same parsed specifiers with -// different sets of arguments (precompilation of format strings). -template -struct dynamic_format_specs : basic_format_specs { - arg_ref width_ref; - arg_ref precision_ref; -}; - -struct auto_id {}; - -// A format specifier handler that sets fields in basic_format_specs. -template class specs_setter { - protected: - basic_format_specs& specs_; - - public: - explicit FMT_CONSTEXPR specs_setter(basic_format_specs& specs) - : specs_(specs) {} - - FMT_CONSTEXPR specs_setter(const specs_setter& other) - : specs_(other.specs_) {} - - FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } - FMT_CONSTEXPR void on_fill(basic_string_view fill) { - specs_.fill = fill; - } - FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; } - FMT_CONSTEXPR void on_hash() { specs_.alt = true; } - FMT_CONSTEXPR void on_localized() { specs_.localized = true; } - - FMT_CONSTEXPR void on_zero() { - if (specs_.align == align::none) specs_.align = align::numeric; - specs_.fill[0] = Char('0'); - } - - FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } - FMT_CONSTEXPR void on_precision(int precision) { - specs_.precision = precision; - } - FMT_CONSTEXPR void end_precision() {} - - FMT_CONSTEXPR void on_type(presentation_type type) { specs_.type = type; } -}; - -// Format spec handler that saves references to arguments representing dynamic -// width and precision to be resolved at formatting time. -template -class dynamic_specs_handler - : public specs_setter { - public: - using char_type = typename ParseContext::char_type; - - FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs& specs, - ParseContext& ctx) - : specs_setter(specs), specs_(specs), context_(ctx) {} - - FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other) - : specs_setter(other), - specs_(other.specs_), - context_(other.context_) {} - - template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - specs_.width_ref = make_arg_ref(arg_id); - } - - template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - specs_.precision_ref = make_arg_ref(arg_id); - } - - FMT_CONSTEXPR void on_error(const char* message) { - context_.on_error(message); - } - - private: - dynamic_format_specs& specs_; - ParseContext& context_; - - using arg_ref_type = arg_ref; - - FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type { - context_.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type { - return arg_ref_type(context_.next_arg_id()); - } - - FMT_CONSTEXPR auto make_arg_ref(basic_string_view arg_id) - -> arg_ref_type { - context_.check_arg_id(arg_id); - basic_string_view format_str( - context_.begin(), to_unsigned(context_.end() - context_.begin())); - return arg_ref_type(arg_id); - } -}; - -template constexpr bool is_ascii_letter(Char c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); -} - -// Converts a character to ASCII. Returns a number > 127 on conversion failure. -template ::value)> -constexpr auto to_ascii(Char value) -> Char { - return value; -} -template ::value)> -constexpr auto to_ascii(Char value) -> - typename std::underlying_type::type { - return value; -} - -template -FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { - if (const_check(sizeof(Char) != 1)) return 1; - auto lengths = - "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4"; - int len = lengths[static_cast(*begin) >> 3]; - - // Compute the pointer to the next character early so that the next - // iteration can start working on the next character. Neither Clang - // nor GCC figure out this reordering on their own. - return len + !len; -} - -// Return the result via the out param to workaround gcc bug 77539. -template -FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { - for (out = first; out != last; ++out) { - if (*out == value) return true; - } - return false; -} - -template <> -inline auto find(const char* first, const char* last, char value, - const char*& out) -> bool { - out = static_cast( - std::memchr(first, value, to_unsigned(last - first))); - return out != nullptr; -} - -// Parses the range [begin, end) as an unsigned integer. This function assumes -// that the range is non-empty and the first character is a digit. -template -FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, - int error_value) noexcept -> int { - FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); - unsigned value = 0, prev = 0; - auto p = begin; - do { - prev = value; - value = value * 10 + unsigned(*p - '0'); - ++p; - } while (p != end && '0' <= *p && *p <= '9'); - auto num_digits = p - begin; - begin = p; - if (num_digits <= std::numeric_limits::digits10) - return static_cast(value); - // Check for overflow. - const unsigned max = to_unsigned((std::numeric_limits::max)()); - return num_digits == std::numeric_limits::digits10 + 1 && - prev * 10ull + unsigned(p[-1] - '0') <= max - ? static_cast(value) - : error_value; -} - -// Parses fill and alignment. -template -FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - FMT_ASSERT(begin != end, ""); - auto align = align::none; - auto p = begin + code_point_length(begin); - if (p >= end) p = begin; - for (;;) { - switch (to_ascii(*p)) { - case '<': - align = align::left; - break; - case '>': - align = align::right; - break; - case '^': - align = align::center; - break; - default: - break; - } - if (align != align::none) { - if (p != begin) { - auto c = *begin; - if (c == '{') - return handler.on_error("invalid fill character '{'"), begin; - handler.on_fill(basic_string_view(begin, to_unsigned(p - begin))); - begin = p + 1; - } else - ++begin; - handler.on_align(align); - break; - } else if (p == begin) { - break; - } - p = begin; - } - return begin; -} - -template FMT_CONSTEXPR bool is_name_start(Char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; -} - -template -FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, - IDHandler&& handler) -> const Char* { - FMT_ASSERT(begin != end, ""); - Char c = *begin; - if (c >= '0' && c <= '9') { - int index = 0; - if (c != '0') - index = - parse_nonnegative_int(begin, end, (std::numeric_limits::max)()); - else - ++begin; - if (begin == end || (*begin != '}' && *begin != ':')) - handler.on_error("invalid format string"); - else - handler(index); - return begin; - } - if (!is_name_start(c)) { - handler.on_error("invalid format string"); - return begin; - } - auto it = begin; - do { - ++it; - } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9'))); - handler(basic_string_view(begin, to_unsigned(it - begin))); - return it; -} - -template -FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end, - IDHandler&& handler) -> const Char* { - Char c = *begin; - if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); - handler(); - return begin; -} - -template -FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - using detail::auto_id; - struct width_adapter { - Handler& handler; - - FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } - FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); } - FMT_CONSTEXPR void operator()(basic_string_view id) { - handler.on_dynamic_width(id); - } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } - }; - - FMT_ASSERT(begin != end, ""); - if ('0' <= *begin && *begin <= '9') { - int width = parse_nonnegative_int(begin, end, -1); - if (width != -1) - handler.on_width(width); - else - handler.on_error("number is too big"); - } else if (*begin == '{') { - ++begin; - if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler}); - if (begin == end || *begin != '}') - return handler.on_error("invalid format string"), begin; - ++begin; - } - return begin; -} - -template -FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - using detail::auto_id; - struct precision_adapter { - Handler& handler; - - FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } - FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); } - FMT_CONSTEXPR void operator()(basic_string_view id) { - handler.on_dynamic_precision(id); - } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } - }; - - ++begin; - auto c = begin != end ? *begin : Char(); - if ('0' <= c && c <= '9') { - auto precision = parse_nonnegative_int(begin, end, -1); - if (precision != -1) - handler.on_precision(precision); - else - handler.on_error("number is too big"); - } else if (c == '{') { - ++begin; - if (begin != end) - begin = parse_arg_id(begin, end, precision_adapter{handler}); - if (begin == end || *begin++ != '}') - return handler.on_error("invalid format string"), begin; - } else { - return handler.on_error("missing precision specifier"), begin; - } - handler.end_precision(); - return begin; -} - -template -FMT_CONSTEXPR auto parse_presentation_type(Char type) -> presentation_type { - switch (to_ascii(type)) { - case 'd': - return presentation_type::dec; - case 'o': - return presentation_type::oct; - case 'x': - return presentation_type::hex_lower; - case 'X': - return presentation_type::hex_upper; - case 'b': - return presentation_type::bin_lower; - case 'B': - return presentation_type::bin_upper; - case 'a': - return presentation_type::hexfloat_lower; - case 'A': - return presentation_type::hexfloat_upper; - case 'e': - return presentation_type::exp_lower; - case 'E': - return presentation_type::exp_upper; - case 'f': - return presentation_type::fixed_lower; - case 'F': - return presentation_type::fixed_upper; - case 'g': - return presentation_type::general_lower; - case 'G': - return presentation_type::general_upper; - case 'c': - return presentation_type::chr; - case 's': - return presentation_type::string; - case 'p': - return presentation_type::pointer; - default: - return presentation_type::none; - } -} - -// Parses standard format specifiers and sends notifications about parsed -// components to handler. -template -FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, - const Char* end, - SpecHandler&& handler) - -> const Char* { - if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) && - *begin != 'L') { - presentation_type type = parse_presentation_type(*begin++); - if (type == presentation_type::none) - handler.on_error("invalid type specifier"); - handler.on_type(type); - return begin; - } - - if (begin == end) return begin; - - begin = parse_align(begin, end, handler); - if (begin == end) return begin; - - // Parse sign. - switch (to_ascii(*begin)) { - case '+': - handler.on_sign(sign::plus); - ++begin; - break; - case '-': - handler.on_sign(sign::minus); - ++begin; - break; - case ' ': - handler.on_sign(sign::space); - ++begin; - break; - default: - break; - } - if (begin == end) return begin; - - if (*begin == '#') { - handler.on_hash(); - if (++begin == end) return begin; - } - - // Parse zero flag. - if (*begin == '0') { - handler.on_zero(); - if (++begin == end) return begin; - } - - begin = parse_width(begin, end, handler); - if (begin == end) return begin; - - // Parse precision. - if (*begin == '.') { - begin = parse_precision(begin, end, handler); - if (begin == end) return begin; - } - - if (*begin == 'L') { - handler.on_localized(); - ++begin; - } - - // Parse type. - if (begin != end && *begin != '}') { - presentation_type type = parse_presentation_type(*begin++); - if (type == presentation_type::none) - handler.on_error("invalid type specifier"); - handler.on_type(type); - } - return begin; -} - -template -FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - struct id_adapter { - Handler& handler; - int arg_id; - - FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); } - FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); } - FMT_CONSTEXPR void operator()(basic_string_view id) { - arg_id = handler.on_arg_id(id); - } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } - }; - - ++begin; - if (begin == end) return handler.on_error("invalid format string"), end; - if (*begin == '}') { - handler.on_replacement_field(handler.on_arg_id(), begin); - } else if (*begin == '{') { - handler.on_text(begin, begin + 1); - } else { - auto adapter = id_adapter{handler, 0}; - begin = parse_arg_id(begin, end, adapter); - Char c = begin != end ? *begin : Char(); - if (c == '}') { - handler.on_replacement_field(adapter.arg_id, begin); - } else if (c == ':') { - begin = handler.on_format_specs(adapter.arg_id, begin + 1, end); - if (begin == end || *begin != '}') - return handler.on_error("unknown format specifier"), end; - } else { - return handler.on_error("missing '}' in format string"), end; - } - } - return begin + 1; -} - -template -FMT_CONSTEXPR FMT_INLINE void parse_format_string( - basic_string_view format_str, Handler&& handler) { - // Workaround a name-lookup bug in MSVC's modules implementation. - using detail::find; - - auto begin = format_str.data(); - auto end = begin + format_str.size(); - if (end - begin < 32) { - // Use a simple loop instead of memchr for small strings. - const Char* p = begin; - while (p != end) { - auto c = *p++; - if (c == '{') { - handler.on_text(begin, p - 1); - begin = p = parse_replacement_field(p - 1, end, handler); - } else if (c == '}') { - if (p == end || *p != '}') - return handler.on_error("unmatched '}' in format string"); - handler.on_text(begin, p); - begin = ++p; - } - } - handler.on_text(begin, end); - return; - } - struct writer { - FMT_CONSTEXPR void operator()(const Char* pbegin, const Char* pend) { - if (pbegin == pend) return; - for (;;) { - const Char* p = nullptr; - if (!find(pbegin, pend, Char('}'), p)) - return handler_.on_text(pbegin, pend); - ++p; - if (p == pend || *p != '}') - return handler_.on_error("unmatched '}' in format string"); - handler_.on_text(pbegin, p); - pbegin = p + 1; - } - } - Handler& handler_; - } write{handler}; - while (begin != end) { - // Doing two passes with memchr (one for '{' and another for '}') is up to - // 2.5x faster than the naive one-pass implementation on big format strings. - const Char* p = begin; - if (*begin != '{' && !find(begin + 1, end, Char('{'), p)) - return write(begin, end); - write(begin, p); - begin = parse_replacement_field(p, end, handler); - } -} - -template -FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) - -> decltype(ctx.begin()) { - using char_type = typename ParseContext::char_type; - using context = buffer_context; - using mapped_type = conditional_t< - mapped_type_constant::value != type::custom_type, - decltype(arg_mapper().map(std::declval())), T>; - auto f = conditional_t::value, - formatter, - fallback_formatter>(); - return f.parse(ctx); -} - -// A parse context with extra argument id checks. It is only used at compile -// time because adding checks at runtime would introduce substantial overhead -// and would be redundant since argument ids are checked when arguments are -// retrieved anyway. -template -class compile_parse_context - : public basic_format_parse_context { - private: - int num_args_; - using base = basic_format_parse_context; - - public: - explicit FMT_CONSTEXPR compile_parse_context( - basic_string_view format_str, - int num_args = (std::numeric_limits::max)(), ErrorHandler eh = {}) - : base(format_str, eh), num_args_(num_args) {} - - FMT_CONSTEXPR auto next_arg_id() -> int { - int id = base::next_arg_id(); - if (id >= num_args_) this->on_error("argument not found"); - return id; - } - - FMT_CONSTEXPR void check_arg_id(int id) { - base::check_arg_id(id); - if (id >= num_args_) this->on_error("argument not found"); - } - using base::check_arg_id; -}; - -template -FMT_CONSTEXPR void check_int_type_spec(presentation_type type, - ErrorHandler&& eh) { - if (type > presentation_type::bin_upper && type != presentation_type::chr) - eh.on_error("invalid type specifier"); -} - -// Checks char specs and returns true if the type spec is char (and not int). -template -FMT_CONSTEXPR auto check_char_specs(const basic_format_specs& specs, - ErrorHandler&& eh = {}) -> bool { - if (specs.type != presentation_type::none && - specs.type != presentation_type::chr) { - check_int_type_spec(specs.type, eh); - return false; - } - if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) - eh.on_error("invalid format specifier for char"); - return true; -} - -// A floating-point presentation format. -enum class float_format : unsigned char { - general, // General: exponent notation or fixed point based on magnitude. - exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. - fixed, // Fixed point with the default precision of 6, e.g. 0.0012. - hex -}; - -struct float_specs { - int precision; - float_format format : 8; - sign_t sign : 8; - bool upper : 1; - bool locale : 1; - bool binary32 : 1; - bool use_grisu : 1; - bool showpoint : 1; -}; - -template -FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs& specs, - ErrorHandler&& eh = {}) - -> float_specs { - auto result = float_specs(); - result.showpoint = specs.alt; - result.locale = specs.localized; - switch (specs.type) { - case presentation_type::none: - result.format = float_format::general; - break; - case presentation_type::general_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::general_lower: - result.format = float_format::general; - break; - case presentation_type::exp_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::exp_lower: - result.format = float_format::exp; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::fixed_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::fixed_lower: - result.format = float_format::fixed; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::hexfloat_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::hexfloat_lower: - result.format = float_format::hex; - break; - default: - eh.on_error("invalid type specifier"); - break; - } - return result; -} - -template -FMT_CONSTEXPR auto check_cstring_type_spec(presentation_type type, - ErrorHandler&& eh = {}) -> bool { - if (type == presentation_type::none || type == presentation_type::string) - return true; - if (type != presentation_type::pointer) eh.on_error("invalid type specifier"); - return false; -} - -template -FMT_CONSTEXPR void check_string_type_spec(presentation_type type, - ErrorHandler&& eh = {}) { - if (type != presentation_type::none && type != presentation_type::string) - eh.on_error("invalid type specifier"); -} - -template -FMT_CONSTEXPR void check_pointer_type_spec(presentation_type type, - ErrorHandler&& eh) { - if (type != presentation_type::none && type != presentation_type::pointer) - eh.on_error("invalid type specifier"); -} - -// A parse_format_specs handler that checks if specifiers are consistent with -// the argument type. -template class specs_checker : public Handler { - private: - detail::type arg_type_; - - FMT_CONSTEXPR void require_numeric_argument() { - if (!is_arithmetic_type(arg_type_)) - this->on_error("format specifier requires numeric argument"); - } - - public: - FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) - : Handler(handler), arg_type_(arg_type) {} - - FMT_CONSTEXPR void on_align(align_t align) { - if (align == align::numeric) require_numeric_argument(); - Handler::on_align(align); - } - - FMT_CONSTEXPR void on_sign(sign_t s) { - require_numeric_argument(); - if (is_integral_type(arg_type_) && arg_type_ != type::int_type && - arg_type_ != type::long_long_type && arg_type_ != type::char_type) { - this->on_error("format specifier requires signed argument"); - } - Handler::on_sign(s); - } - - FMT_CONSTEXPR void on_hash() { - require_numeric_argument(); - Handler::on_hash(); - } - - FMT_CONSTEXPR void on_localized() { - require_numeric_argument(); - Handler::on_localized(); - } - - FMT_CONSTEXPR void on_zero() { - require_numeric_argument(); - Handler::on_zero(); - } - - FMT_CONSTEXPR void end_precision() { - if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) - this->on_error("precision not allowed for this argument type"); - } -}; - -constexpr int invalid_arg_index = -1; - -#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS -template -constexpr auto get_arg_index_by_name(basic_string_view name) -> int { - if constexpr (detail::is_statically_named_arg()) { - if (name == T::name) return N; - } - if constexpr (sizeof...(Args) > 0) - return get_arg_index_by_name(name); - (void)name; // Workaround an MSVC bug about "unused" parameter. - return invalid_arg_index; -} -#endif - -template -FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { -#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS - if constexpr (sizeof...(Args) > 0) - return get_arg_index_by_name<0, Args...>(name); -#endif - (void)name; - return invalid_arg_index; -} - -template -class format_string_checker { - private: - using parse_context_type = compile_parse_context; - enum { num_args = sizeof...(Args) }; - - // Format specifier parsing function. - using parse_func = const Char* (*)(parse_context_type&); - - parse_context_type context_; - parse_func parse_funcs_[num_args > 0 ? num_args : 1]; - - public: - explicit FMT_CONSTEXPR format_string_checker( - basic_string_view format_str, ErrorHandler eh) - : context_(format_str, num_args, eh), - parse_funcs_{&parse_format_specs...} {} - - FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - - FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } - FMT_CONSTEXPR auto on_arg_id(int id) -> int { - return context_.check_arg_id(id), id; - } - FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { -#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS - auto index = get_arg_index_by_name(id); - if (index == invalid_arg_index) on_error("named argument is not found"); - return context_.check_arg_id(index), index; -#else - (void)id; - on_error("compile-time checks for named arguments require C++20 support"); - return 0; -#endif - } - - FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} - - FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) - -> const Char* { - context_.advance_to(context_.begin() + (begin - &*context_.begin())); - // id >= 0 check is a workaround for gcc 10 bug (#2065). - return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; - } - - FMT_CONSTEXPR void on_error(const char* message) { - context_.on_error(message); - } -}; - -template ::value), int>> -void check_format_string(S format_str) { - FMT_CONSTEXPR auto s = to_string_view(format_str); - using checker = format_string_checker...>; - FMT_CONSTEXPR bool invalid_format = - (parse_format_string(s, checker(s, {})), true); - ignore_unused(invalid_format); -} - -template -void vformat_to( - buffer& buf, basic_string_view fmt, - basic_format_args)> args, - locale_ref loc = {}); - -FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); -#ifndef _WIN32 -inline void vprint_mojibake(std::FILE*, string_view, format_args) {} -#endif -FMT_END_DETAIL_NAMESPACE - -// A formatter specialization for the core types corresponding to detail::type -// constants. -template -struct formatter::value != - detail::type::custom_type>> { - private: - detail::dynamic_format_specs specs_; - - public: - // Parses format specifiers stopping either at the end of the range or at the - // terminating '}'. - template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin == end) return begin; - using handler_type = detail::dynamic_specs_handler; - auto type = detail::type_constant::value; - auto checker = - detail::specs_checker(handler_type(specs_, ctx), type); - auto it = detail::parse_format_specs(begin, end, checker); - auto eh = ctx.error_handler(); - switch (type) { - case detail::type::none_type: - FMT_ASSERT(false, "invalid argument type"); - break; - case detail::type::bool_type: - if (specs_.type == presentation_type::none || - specs_.type == presentation_type::string) { - break; - } - FMT_FALLTHROUGH; - case detail::type::int_type: - case detail::type::uint_type: - case detail::type::long_long_type: - case detail::type::ulong_long_type: - case detail::type::int128_type: - case detail::type::uint128_type: - detail::check_int_type_spec(specs_.type, eh); - break; - case detail::type::char_type: - detail::check_char_specs(specs_, eh); - break; - case detail::type::float_type: - if (detail::const_check(FMT_USE_FLOAT)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "float support disabled"); - break; - case detail::type::double_type: - if (detail::const_check(FMT_USE_DOUBLE)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "double support disabled"); - break; - case detail::type::long_double_type: - if (detail::const_check(FMT_USE_LONG_DOUBLE)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "long double support disabled"); - break; - case detail::type::cstring_type: - detail::check_cstring_type_spec(specs_.type, eh); - break; - case detail::type::string_type: - detail::check_string_type_spec(specs_.type, eh); - break; - case detail::type::pointer_type: - detail::check_pointer_type_spec(specs_.type, eh); - break; - case detail::type::custom_type: - // Custom format specifiers are checked in parse functions of - // formatter specializations. - break; - } - return it; - } - - template - FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const - -> decltype(ctx.out()); -}; - -template struct basic_runtime { basic_string_view str; }; - -/** A compile-time format string. */ -template class basic_format_string { - private: - basic_string_view str_; - - public: - template >::value)> - FMT_CONSTEVAL FMT_INLINE basic_format_string(const S& s) : str_(s) { - static_assert( - detail::count< - (std::is_base_of>::value && - std::is_reference::value)...>() == 0, - "passing views as lvalues is disallowed"); -#ifdef FMT_HAS_CONSTEVAL - if constexpr (detail::count_named_args() == 0) { - using checker = detail::format_string_checker...>; - detail::parse_format_string(str_, checker(s, {})); - } -#else - detail::check_format_string(s); -#endif - } - basic_format_string(basic_runtime r) : str_(r.str) {} - - FMT_INLINE operator basic_string_view() const { return str_; } -}; - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 -// Workaround broken conversion on older gcc. -template using format_string = string_view; -template auto runtime(const S& s) -> basic_string_view> { - return s; -} -#else -template -using format_string = basic_format_string...>; -/** - \rst - Creates a runtime format string. - - **Example**:: - - // Check format string at runtime instead of compile-time. - fmt::print(fmt::runtime("{:d}"), "I am not a number"); - \endrst - */ -template auto runtime(const S& s) -> basic_runtime> { - return {{s}}; -} -#endif - -FMT_API auto vformat(string_view fmt, format_args args) -> std::string; - -/** - \rst - Formats ``args`` according to specifications in ``fmt`` and returns the result - as a string. - - **Example**:: - - #include - std::string message = fmt::format("The answer is {}.", 42); - \endrst -*/ -template -FMT_INLINE auto format(format_string fmt, T&&... args) -> std::string { - return vformat(fmt, fmt::make_format_args(args...)); -} - -/** Formats a string and writes the output to ``out``. */ -template ::value)> -auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { - using detail::get_buffer; - auto&& buf = get_buffer(out); - detail::vformat_to(buf, fmt, args, {}); - return detail::get_iterator(buf); -} - -/** - \rst - Formats ``args`` according to specifications in ``fmt``, writes the result to - the output iterator ``out`` and returns the iterator past the end of the output - range. `format_to` does not append a terminating null character. - - **Example**:: - - auto out = std::vector(); - fmt::format_to(std::back_inserter(out), "{}", 42); - \endrst - */ -template ::value)> -FMT_INLINE auto format_to(OutputIt out, format_string fmt, T&&... args) - -> OutputIt { - return vformat_to(out, fmt, fmt::make_format_args(args...)); -} - -template struct format_to_n_result { - /** Iterator past the end of the output range. */ - OutputIt out; - /** Total (not truncated) output size. */ - size_t size; -}; - -template ::value)> -auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) - -> format_to_n_result { - using traits = detail::fixed_buffer_traits; - auto buf = detail::iterator_buffer(out, n); - detail::vformat_to(buf, fmt, args, {}); - return {buf.out(), buf.count()}; -} - -/** - \rst - Formats ``args`` according to specifications in ``fmt``, writes up to ``n`` - characters of the result to the output iterator ``out`` and returns the total - (not truncated) output size and the iterator past the end of the output range. - `format_to_n` does not append a terminating null character. - \endrst - */ -template ::value)> -FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, - T&&... args) -> format_to_n_result { - return vformat_to_n(out, n, fmt, fmt::make_format_args(args...)); -} - -/** Returns the number of chars in the output of ``format(fmt, args...)``. */ -template -FMT_INLINE auto formatted_size(format_string fmt, T&&... args) -> size_t { - auto buf = detail::counting_buffer<>(); - detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...), {}); - return buf.count(); -} - -FMT_API void vprint(string_view fmt, format_args args); -FMT_API void vprint(std::FILE* f, string_view fmt, format_args args); - -/** - \rst - Formats ``args`` according to specifications in ``fmt`` and writes the output - to ``stdout``. - - **Example**:: - - fmt::print("Elapsed time: {0:.2f} seconds", 1.23); - \endrst - */ -template -FMT_INLINE void print(format_string fmt, T&&... args) { - const auto& vargs = fmt::make_format_args(args...); - return detail::is_utf8() ? vprint(fmt, vargs) - : detail::vprint_mojibake(stdout, fmt, vargs); -} - -/** - \rst - Formats ``args`` according to specifications in ``fmt`` and writes the - output to the file ``f``. - - **Example**:: - - fmt::print(stderr, "Don't {}!", "panic"); - \endrst - */ -template -FMT_INLINE void print(std::FILE* f, format_string fmt, T&&... args) { - const auto& vargs = fmt::make_format_args(args...); - return detail::is_utf8() ? vprint(f, fmt, vargs) - : detail::vprint_mojibake(f, fmt, vargs); -} - -FMT_MODULE_EXPORT_END -FMT_GCC_PRAGMA("GCC pop_options") -FMT_END_NAMESPACE - -#ifdef FMT_HEADER_ONLY -# include "format.h" -#endif -#endif // FMT_CORE_H_ +#include "format.h" diff --git a/src/3rdparty/fmt/format-inl.h b/src/3rdparty/fmt/format-inl.h index b7d88270..73f40f3d 100644 --- a/src/3rdparty/fmt/format-inl.h +++ b/src/3rdparty/fmt/format-inl.h @@ -8,21 +8,19 @@ #ifndef FMT_FORMAT_INL_H_ #define FMT_FORMAT_INL_H_ -#include -#include +#ifndef FMT_IMPORT_STD +# include +# include +# include +#endif #include // errno #include -#include -#include -#include // std::memmove -#include -#include -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) && !defined(FMT_IMPORT_STD) # include #endif -#ifdef _WIN32 +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) # include // _isatty #endif @@ -40,25 +38,8 @@ FMT_FUNC void assert_fail(const char* file, int line, const char* message) { std::terminate(); } -FMT_FUNC void throw_format_error(const char* message) { - FMT_THROW(format_error(message)); -} - -#ifndef _MSC_VER -# define FMT_SNPRINTF snprintf -#else // _MSC_VER -inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) { - va_list args; - va_start(args, format); - int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); - va_end(args); - return result; -} -# define FMT_SNPRINTF fmt_snprintf -#endif // _MSC_VER - FMT_FUNC void format_error_code(detail::buffer& out, int error_code, - string_view message) FMT_NOEXCEPT { + string_view message) noexcept { // Report error code making sure that the output fits into // inline_buffer_size to avoid dynamic memory allocation and potential // bad_alloc. @@ -73,15 +54,15 @@ FMT_FUNC void format_error_code(detail::buffer& out, int error_code, ++error_code_size; } error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); - auto it = buffer_appender(out); + auto it = appender(out); if (message.size() <= inline_buffer_size - error_code_size) - format_to(it, FMT_STRING("{}{}"), message, SEP); - format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); + fmt::format_to(it, FMT_STRING("{}{}"), message, SEP); + fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); FMT_ASSERT(out.size() <= inline_buffer_size, ""); } FMT_FUNC void report_error(format_func func, int error_code, - const char* message) FMT_NOEXCEPT { + const char* message) noexcept { memory_buffer full_message; func(full_message, error_code, message); // Don't use fwrite_fully because the latter may throw. @@ -90,10 +71,10 @@ FMT_FUNC void report_error(format_func func, int error_code, } // A wrapper around fwrite that throws on error. -inline void fwrite_fully(const void* ptr, size_t size, size_t count, - FILE* stream) { - size_t written = std::fwrite(ptr, size, count, stream); - if (written < count) FMT_THROW(system_error(errno, "cannot write to file")); +inline void fwrite_fully(const void* ptr, size_t count, FILE* stream) { + size_t written = std::fwrite(ptr, 1, count, stream); + if (written < count) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); } #ifndef FMT_STATIC_THOUSANDS_SEPARATOR @@ -102,7 +83,7 @@ locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { static_assert(std::is_same::value, ""); } -template Locale locale_ref::get() const { +template auto locale_ref::get() const -> Locale { static_assert(std::is_same::value, ""); return locale_ ? *static_cast(locale_) : std::locale(); } @@ -114,7 +95,8 @@ FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); return {std::move(grouping), thousands_sep}; } -template FMT_FUNC Char decimal_point_impl(locale_ref loc) { +template +FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char { return std::use_facet>(loc.get()) .decimal_point(); } @@ -127,951 +109,162 @@ template FMT_FUNC Char decimal_point_impl(locale_ref) { return '.'; } #endif -} // namespace detail -#if !FMT_MSC_VER -FMT_API FMT_FUNC format_error::~format_error() FMT_NOEXCEPT = default; +FMT_FUNC auto write_loc(appender out, loc_value value, + const format_specs& specs, locale_ref loc) -> bool { +#ifdef FMT_STATIC_THOUSANDS_SEPARATOR + value.visit(loc_writer<>{ + out, specs, std::string(1, FMT_STATIC_THOUSANDS_SEPARATOR), "\3", "."}); + return true; +#else + auto locale = loc.get(); + // We cannot use the num_put facet because it may produce output in + // a wrong encoding. + using facet = format_facet; + if (std::has_facet(locale)) + return std::use_facet(locale).put(out, value, specs); + return facet(locale).put(out, value, specs); #endif - -FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str, - format_args args) { - auto ec = std::error_code(error_code, std::generic_category()); - return std::system_error(ec, vformat(format_str, args)); -} - -namespace detail { - -template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) { - // fallback_uintptr is always stored in little endian. - int i = static_cast(sizeof(void*)) - 1; - while (i > 0 && n.value[i] == 0) --i; - auto char_digits = std::numeric_limits::digits / 4; - return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1; } +} // namespace detail -// log10(2) = 0x0.4d104d427de7fbcc... -static constexpr uint64_t log10_2_significand = 0x4d104d427de7fbcc; - -template struct basic_impl_data { - // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. - // These are generated by support/compute-powers.py. - static constexpr uint64_t pow10_significands[87] = { - 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, - 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, - 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, - 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, - 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, - 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, - 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, - 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, - 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, - 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, - 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, - 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, - 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, - 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, - 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, - 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, - 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, - 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, - 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, - 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, - 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, - 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, - 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, - 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, - 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, - 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, - 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, - 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, - 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, - }; - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - FMT_GCC_PRAGMA("GCC diagnostic push") - FMT_GCC_PRAGMA("GCC diagnostic ignored \"-Wnarrowing\"") -#endif - // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding - // to significands above. - static constexpr int16_t pow10_exponents[87] = { - -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, - -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, - -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, - -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, - -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, - 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, - 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, - 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - FMT_GCC_PRAGMA("GCC diagnostic pop") -#endif - - static constexpr uint64_t power_of_10_64[20] = { - 1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL), - 10000000000000000000ULL}; -}; - -// This is a struct rather than an alias to avoid shadowing warnings in gcc. -struct impl_data : basic_impl_data<> {}; - -#if __cplusplus < 201703L -template -constexpr uint64_t basic_impl_data::pow10_significands[]; -template constexpr int16_t basic_impl_data::pow10_exponents[]; -template constexpr uint64_t basic_impl_data::power_of_10_64[]; -#endif - -template struct bits { - static FMT_CONSTEXPR_DECL const int value = - static_cast(sizeof(T) * std::numeric_limits::digits); -}; - -class fp; -template FMT_CONSTEXPR fp normalize(fp value); - -// Lower (upper) boundary is a value half way between a floating-point value -// and its predecessor (successor). Boundaries have the same exponent as the -// value so only significands are stored. -struct boundaries { - uint64_t lower; - uint64_t upper; -}; - -// A handmade floating-point number f * pow(2, e). -class fp { - private: - using significand_type = uint64_t; - - template - using is_supported_float = bool_constant; - - public: - significand_type f; - int e; - - // All sizes are in bits. - // Subtract 1 to account for an implicit most significant bit in the - // normalized form. - static FMT_CONSTEXPR_DECL const int double_significand_size = - std::numeric_limits::digits - 1; - static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = - 1ULL << double_significand_size; - static FMT_CONSTEXPR_DECL const int significand_size = - bits::value; - - constexpr fp() : f(0), e(0) {} - constexpr fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} - - // Constructs fp from an IEEE754 double. It is a template to prevent compile - // errors on platforms where double is not IEEE754. - template explicit FMT_CONSTEXPR fp(Double d) { assign(d); } - - // Assigns d to this and return true iff predecessor is closer than successor. - template ::value)> - FMT_CONSTEXPR bool assign(Float d) { - // Assume float is in the format [sign][exponent][significand]. - using limits = std::numeric_limits; - const int float_significand_size = limits::digits - 1; - const int exponent_size = - bits::value - float_significand_size - 1; // -1 for sign - const uint64_t float_implicit_bit = 1ULL << float_significand_size; - const uint64_t significand_mask = float_implicit_bit - 1; - const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask; - const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; - constexpr bool is_double = sizeof(Float) == sizeof(uint64_t); - auto u = bit_cast>(d); - f = u & significand_mask; - int biased_e = - static_cast((u & exponent_mask) >> float_significand_size); - // Predecessor is closer if d is a normalized power of 2 (f == 0) other than - // the smallest normalized number (biased_e > 1). - bool is_predecessor_closer = f == 0 && biased_e > 1; - if (biased_e != 0) - f += float_implicit_bit; - else - biased_e = 1; // Subnormals use biased exponent 1 (min exponent). - e = biased_e - exponent_bias - float_significand_size; - return is_predecessor_closer; - } - - template ::value)> - bool assign(Float) { - *this = fp(); - return false; - } -}; - -// Normalizes the value converted from double and multiplied by (1 << SHIFT). -template FMT_CONSTEXPR fp normalize(fp value) { - // Handle subnormals. - const auto shifted_implicit_bit = fp::implicit_bit << SHIFT; - while ((value.f & shifted_implicit_bit) == 0) { - value.f <<= 1; - --value.e; - } - // Subtract 1 to account for hidden bit. - const auto offset = - fp::significand_size - fp::double_significand_size - SHIFT - 1; - value.f <<= offset; - value.e -= offset; - return value; +FMT_FUNC void report_error(const char* message) { + FMT_THROW(format_error(message)); } -inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; } +template typename Locale::id format_facet::id; -// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. -FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { -#if FMT_USE_INT128 - auto product = static_cast<__uint128_t>(lhs) * rhs; - auto f = static_cast(product >> 64); - return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; -#else - // Multiply 32-bit parts of significands. - uint64_t mask = (1ULL << 32) - 1; - uint64_t a = lhs >> 32, b = lhs & mask; - uint64_t c = rhs >> 32, d = rhs & mask; - uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; - // Compute mid 64-bit of result and round. - uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); - return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); -#endif +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +template format_facet::format_facet(Locale& loc) { + auto& numpunct = std::use_facet>(loc); + grouping_ = numpunct.grouping(); + if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep()); } -FMT_CONSTEXPR inline fp operator*(fp x, fp y) { - return {multiply(x.f, y.f), x.e + y.e + 64}; +template <> +FMT_API FMT_FUNC auto format_facet::do_put( + appender out, loc_value val, const format_specs& specs) const -> bool { + return val.visit( + detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}); } +#endif -// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its -// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. -FMT_CONSTEXPR inline fp get_cached_power(int min_exponent, - int& pow10_exponent) { - const int shift = 32; - const auto significand = static_cast(log10_2_significand); - int index = static_cast( - ((min_exponent + fp::significand_size - 1) * (significand >> shift) + - ((int64_t(1) << shift) - 1)) // ceil - >> 32 // arithmetic shift - ); - // Decimal exponent of the first (smallest) cached power of 10. - const int first_dec_exp = -348; - // Difference between 2 consecutive decimal exponents in cached powers of 10. - const int dec_exp_step = 8; - index = (index - first_dec_exp - 1) / dec_exp_step + 1; - pow10_exponent = first_dec_exp + index * dec_exp_step; - return {impl_data::pow10_significands[index], - impl_data::pow10_exponents[index]}; +FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args) + -> std::system_error { + auto ec = std::error_code(error_code, std::generic_category()); + return std::system_error(ec, vformat(fmt, args)); } -// A simple accumulator to hold the sums of terms in bigint::square if uint128_t -// is not available. -struct accumulator { - uint64_t lower; - uint64_t upper; - - constexpr accumulator() : lower(0), upper(0) {} - constexpr explicit operator uint32_t() const { - return static_cast(lower); - } - - FMT_CONSTEXPR void operator+=(uint64_t n) { - lower += n; - if (lower < n) ++upper; - } - FMT_CONSTEXPR void operator>>=(int shift) { - FMT_ASSERT(shift == 32, ""); - (void)shift; - lower = (upper << 32) | (lower >> 32); - upper >>= 32; - } -}; - -class bigint { - private: - // A bigint is stored as an array of bigits (big digits), with bigit at index - // 0 being the least significant one. - using bigit = uint32_t; - using double_bigit = uint64_t; - enum { bigits_capacity = 32 }; - basic_memory_buffer bigits_; - int exp_; - - FMT_CONSTEXPR20 bigit operator[](int index) const { - return bigits_[to_unsigned(index)]; - } - FMT_CONSTEXPR20 bigit& operator[](int index) { - return bigits_[to_unsigned(index)]; - } - - static FMT_CONSTEXPR_DECL const int bigit_bits = bits::value; - - friend struct formatter; - - FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) { - auto result = static_cast((*this)[index]) - other - borrow; - (*this)[index] = static_cast(result); - borrow = static_cast(result >> (bigit_bits * 2 - 1)); - } - - FMT_CONSTEXPR20 void remove_leading_zeros() { - int num_bigits = static_cast(bigits_.size()) - 1; - while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; - bigits_.resize(to_unsigned(num_bigits + 1)); - } - - // Computes *this -= other assuming aligned bigints and *this >= other. - FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) { - FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); - FMT_ASSERT(compare(*this, other) >= 0, ""); - bigit borrow = 0; - int i = other.exp_ - exp_; - for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) - subtract_bigits(i, other.bigits_[j], borrow); - while (borrow > 0) subtract_bigits(i, 0, borrow); - remove_leading_zeros(); - } - - FMT_CONSTEXPR20 void multiply(uint32_t value) { - const double_bigit wide_value = value; - bigit carry = 0; - for (size_t i = 0, n = bigits_.size(); i < n; ++i) { - double_bigit result = bigits_[i] * wide_value + carry; - bigits_[i] = static_cast(result); - carry = static_cast(result >> bigit_bits); - } - if (carry != 0) bigits_.push_back(carry); - } - - FMT_CONSTEXPR20 void multiply(uint64_t value) { - const bigit mask = ~bigit(0); - const double_bigit lower = value & mask; - const double_bigit upper = value >> bigit_bits; - double_bigit carry = 0; - for (size_t i = 0, n = bigits_.size(); i < n; ++i) { - double_bigit result = bigits_[i] * lower + (carry & mask); - carry = - bigits_[i] * upper + (result >> bigit_bits) + (carry >> bigit_bits); - bigits_[i] = static_cast(result); - } - while (carry != 0) { - bigits_.push_back(carry & mask); - carry >>= bigit_bits; - } - } - - public: - FMT_CONSTEXPR20 bigint() : exp_(0) {} - explicit bigint(uint64_t n) { assign(n); } - FMT_CONSTEXPR20 ~bigint() { - FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); - } - - bigint(const bigint&) = delete; - void operator=(const bigint&) = delete; - - FMT_CONSTEXPR20 void assign(const bigint& other) { - auto size = other.bigits_.size(); - bigits_.resize(size); - auto data = other.bigits_.data(); - std::copy(data, data + size, make_checked(bigits_.data(), size)); - exp_ = other.exp_; - } - - FMT_CONSTEXPR20 void assign(uint64_t n) { - size_t num_bigits = 0; - do { - bigits_[num_bigits++] = n & ~bigit(0); - n >>= bigit_bits; - } while (n != 0); - bigits_.resize(num_bigits); - exp_ = 0; - } - - FMT_CONSTEXPR20 int num_bigits() const { - return static_cast(bigits_.size()) + exp_; - } - - FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) { - FMT_ASSERT(shift >= 0, ""); - exp_ += shift / bigit_bits; - shift %= bigit_bits; - if (shift == 0) return *this; - bigit carry = 0; - for (size_t i = 0, n = bigits_.size(); i < n; ++i) { - bigit c = bigits_[i] >> (bigit_bits - shift); - bigits_[i] = (bigits_[i] << shift) + carry; - carry = c; - } - if (carry != 0) bigits_.push_back(carry); - return *this; - } - - template FMT_CONSTEXPR20 bigint& operator*=(Int value) { - FMT_ASSERT(value > 0, ""); - multiply(uint32_or_64_or_128_t(value)); - return *this; - } - - friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) { - int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); - if (num_lhs_bigits != num_rhs_bigits) - return num_lhs_bigits > num_rhs_bigits ? 1 : -1; - int i = static_cast(lhs.bigits_.size()) - 1; - int j = static_cast(rhs.bigits_.size()) - 1; - int end = i - j; - if (end < 0) end = 0; - for (; i >= end; --i, --j) { - bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j]; - if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1; - } - if (i != j) return i > j ? 1 : -1; - return 0; - } - - // Returns compare(lhs1 + lhs2, rhs). - friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2, - const bigint& rhs) { - int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits()); - int num_rhs_bigits = rhs.num_bigits(); - if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; - if (max_lhs_bigits > num_rhs_bigits) return 1; - auto get_bigit = [](const bigint& n, int i) -> bigit { - return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0; - }; - double_bigit borrow = 0; - int min_exp = (std::min)((std::min)(lhs1.exp_, lhs2.exp_), rhs.exp_); - for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { - double_bigit sum = - static_cast(get_bigit(lhs1, i)) + get_bigit(lhs2, i); - bigit rhs_bigit = get_bigit(rhs, i); - if (sum > rhs_bigit + borrow) return 1; - borrow = rhs_bigit + borrow - sum; - if (borrow > 1) return -1; - borrow <<= bigit_bits; - } - return borrow != 0 ? -1 : 0; - } - - // Assigns pow(10, exp) to this bigint. - FMT_CONSTEXPR20 void assign_pow10(int exp) { - FMT_ASSERT(exp >= 0, ""); - if (exp == 0) return assign(1); - // Find the top bit. - int bitmask = 1; - while (exp >= bitmask) bitmask <<= 1; - bitmask >>= 1; - // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by - // repeated squaring and multiplication. - assign(5); - bitmask >>= 1; - while (bitmask != 0) { - square(); - if ((exp & bitmask) != 0) *this *= 5; - bitmask >>= 1; - } - *this <<= exp; // Multiply by pow(2, exp) by shifting. - } - - FMT_CONSTEXPR20 void square() { - int num_bigits = static_cast(bigits_.size()); - int num_result_bigits = 2 * num_bigits; - basic_memory_buffer n(std::move(bigits_)); - bigits_.resize(to_unsigned(num_result_bigits)); - using accumulator_t = conditional_t; - auto sum = accumulator_t(); - for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { - // Compute bigit at position bigit_index of the result by adding - // cross-product terms n[i] * n[j] such that i + j == bigit_index. - for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { - // Most terms are multiplied twice which can be optimized in the future. - sum += static_cast(n[i]) * n[j]; - } - (*this)[bigit_index] = static_cast(sum); - sum >>= bits::value; // Compute the carry. - } - // Do the same for the top half. - for (int bigit_index = num_bigits; bigit_index < num_result_bigits; - ++bigit_index) { - for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) - sum += static_cast(n[i++]) * n[j--]; - (*this)[bigit_index] = static_cast(sum); - sum >>= bits::value; - } - remove_leading_zeros(); - exp_ *= 2; - } - - // If this bigint has a bigger exponent than other, adds trailing zero to make - // exponents equal. This simplifies some operations such as subtraction. - FMT_CONSTEXPR20 void align(const bigint& other) { - int exp_difference = exp_ - other.exp_; - if (exp_difference <= 0) return; - int num_bigits = static_cast(bigits_.size()); - bigits_.resize(to_unsigned(num_bigits + exp_difference)); - for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) - bigits_[j] = bigits_[i]; - std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); - exp_ -= exp_difference; - } - - // Divides this bignum by divisor, assigning the remainder to this and - // returning the quotient. - FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) { - FMT_ASSERT(this != &divisor, ""); - if (compare(*this, divisor) < 0) return 0; - FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); - align(divisor); - int quotient = 0; - do { - subtract_aligned(divisor); - ++quotient; - } while (compare(*this, divisor) >= 0); - return quotient; - } -}; +namespace detail { -enum class round_direction { unknown, up, down }; - -// Given the divisor (normally a power of 10), the remainder = v % divisor for -// some number v and the error, returns whether v should be rounded up, down, or -// whether the rounding direction can't be determined due to error. -// error should be less than divisor / 2. -FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor, - uint64_t remainder, - uint64_t error) { - FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow. - FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow. - FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow. - // Round down if (remainder + error) * 2 <= divisor. - if (remainder <= divisor - remainder && error * 2 <= divisor - remainder * 2) - return round_direction::down; - // Round up if (remainder - error) * 2 >= divisor. - if (remainder >= error && - remainder - error >= divisor - (remainder - error)) { - return round_direction::up; - } - return round_direction::unknown; +template +inline auto operator==(basic_fp x, basic_fp y) -> bool { + return x.f == y.f && x.e == y.e; } -namespace digits { -enum result { - more, // Generate more digits. - done, // Done generating digits. - error // Digit generation cancelled due to an error. -}; +// Compilers should be able to optimize this into the ror instruction. +FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { + r &= 31; + return (n >> r) | (n << (32 - r)); } - -// Generates output using the Grisu digit-gen algorithm. -// error: the size of the region (lower, upper) outside of which numbers -// definitely do not round to value (Delta in Grisu3). -template -FMT_INLINE FMT_CONSTEXPR digits::result grisu_gen_digits(fp value, - uint64_t error, - int& exp, - Handler& handler) { - const fp one(1ULL << -value.e, value.e); - // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be - // zero because it contains a product of two 64-bit numbers with MSB set (due - // to normalization) - 1, shifted right by at most 60 bits. - auto integral = static_cast(value.f >> -one.e); - FMT_ASSERT(integral != 0, ""); - FMT_ASSERT(integral == value.f >> -one.e, ""); - // The fractional part of scaled value (p2 in Grisu) c = value % one. - uint64_t fractional = value.f & (one.f - 1); - exp = count_digits(integral); // kappa in Grisu. - // Divide by 10 to prevent overflow. - auto result = handler.on_start(impl_data::power_of_10_64[exp - 1] << -one.e, - value.f / 10, error * 10, exp); - if (result != digits::more) return result; - // Generate digits for the integral part. This can produce up to 10 digits. - do { - uint32_t digit = 0; - auto divmod_integral = [&](uint32_t divisor) { - digit = integral / divisor; - integral %= divisor; - }; - // This optimization by Milo Yip reduces the number of integer divisions by - // one per iteration. - switch (exp) { - case 10: - divmod_integral(1000000000); - break; - case 9: - divmod_integral(100000000); - break; - case 8: - divmod_integral(10000000); - break; - case 7: - divmod_integral(1000000); - break; - case 6: - divmod_integral(100000); - break; - case 5: - divmod_integral(10000); - break; - case 4: - divmod_integral(1000); - break; - case 3: - divmod_integral(100); - break; - case 2: - divmod_integral(10); - break; - case 1: - digit = integral; - integral = 0; - break; - default: - FMT_ASSERT(false, "invalid number of digits"); - } - --exp; - auto remainder = (static_cast(integral) << -one.e) + fractional; - result = handler.on_digit(static_cast('0' + digit), - impl_data::power_of_10_64[exp] << -one.e, - remainder, error, exp, true); - if (result != digits::more) return result; - } while (exp > 0); - // Generate digits for the fractional part. - for (;;) { - fractional *= 10; - error *= 10; - char digit = static_cast('0' + (fractional >> -one.e)); - fractional &= one.f - 1; - --exp; - result = handler.on_digit(digit, one.f, fractional, error, exp, false); - if (result != digits::more) return result; - } +FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { + r &= 63; + return (n >> r) | (n << (64 - r)); } -// The fixed precision digit handler. -struct fixed_handler { - char* buf; - int size; - int precision; - int exp10; - bool fixed; - - FMT_CONSTEXPR digits::result on_start(uint64_t divisor, uint64_t remainder, - uint64_t error, int& exp) { - // Non-fixed formats require at least one digit and no precision adjustment. - if (!fixed) return digits::more; - // Adjust fixed precision by exponent because it is relative to decimal - // point. - precision += exp + exp10; - // Check if precision is satisfied just by leading zeros, e.g. - // format("{:.2f}", 0.001) gives "0.00" without generating any digits. - if (precision > 0) return digits::more; - if (precision < 0) return digits::done; - auto dir = get_round_direction(divisor, remainder, error); - if (dir == round_direction::unknown) return digits::error; - buf[size++] = dir == round_direction::up ? '1' : '0'; - return digits::done; - } - - FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor, - uint64_t remainder, uint64_t error, int, - bool integral) { - FMT_ASSERT(remainder < divisor, ""); - buf[size++] = digit; - if (!integral && error >= remainder) return digits::error; - if (size < precision) return digits::more; - if (!integral) { - // Check if error * 2 < divisor with overflow prevention. - // The check is not needed for the integral part because error = 1 - // and divisor > (1 << 32) there. - if (error >= divisor || error >= divisor - error) return digits::error; - } else { - FMT_ASSERT(error == 1 && divisor > 2, ""); - } - auto dir = get_round_direction(divisor, remainder, error); - if (dir != round_direction::up) - return dir == round_direction::down ? digits::done : digits::error; - ++buf[size - 1]; - for (int i = size - 1; i > 0 && buf[i] > '9'; --i) { - buf[i] = '0'; - ++buf[i - 1]; - } - if (buf[0] > '9') { - buf[0] = '1'; - if (fixed) - buf[size++] = '0'; - else - ++exp10; - } - return digits::done; - } -}; - -// A 128-bit integer type used internally, -struct uint128_wrapper { - uint128_wrapper() = default; - -#if FMT_USE_INT128 - uint128_t internal_; - - constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT - : internal_{static_cast(low) | - (static_cast(high) << 64)} {} - - constexpr uint128_wrapper(uint128_t u) : internal_{u} {} - - constexpr uint64_t high() const FMT_NOEXCEPT { - return uint64_t(internal_ >> 64); - } - constexpr uint64_t low() const FMT_NOEXCEPT { return uint64_t(internal_); } - - uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT { - internal_ += n; - return *this; - } -#else - uint64_t high_; - uint64_t low_; - - constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT - : high_{high}, - low_{low} {} - - constexpr uint64_t high() const FMT_NOEXCEPT { return high_; } - constexpr uint64_t low() const FMT_NOEXCEPT { return low_; } - - uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT { -# if defined(_MSC_VER) && defined(_M_X64) - unsigned char carry = _addcarry_u64(0, low_, n, &low_); - _addcarry_u64(carry, high_, 0, &high_); - return *this; -# else - uint64_t sum = low_ + n; - high_ += (sum < low_ ? 1 : 0); - low_ = sum; - return *this; -# endif - } -#endif -}; - // Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. namespace dragonbox { -// Computes 128-bit result of multiplication of two 64-bit unsigned integers. -inline uint128_wrapper umul128(uint64_t x, uint64_t y) FMT_NOEXCEPT { -#if FMT_USE_INT128 - return static_cast(x) * static_cast(y); -#elif defined(_MSC_VER) && defined(_M_X64) - uint128_wrapper result; - result.low_ = _umul128(x, y, &result.high_); - return result; -#else - const uint64_t mask = (uint64_t(1) << 32) - uint64_t(1); - - uint64_t a = x >> 32; - uint64_t b = x & mask; - uint64_t c = y >> 32; - uint64_t d = y & mask; - - uint64_t ac = a * c; - uint64_t bc = b * c; - uint64_t ad = a * d; - uint64_t bd = b * d; - - uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); - - return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), - (intermediate << 32) + (bd & mask)}; -#endif -} - -// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. -inline uint64_t umul128_upper64(uint64_t x, uint64_t y) FMT_NOEXCEPT { -#if FMT_USE_INT128 - auto p = static_cast(x) * static_cast(y); - return static_cast(p >> 64); -#elif defined(_MSC_VER) && defined(_M_X64) - return __umulh(x, y); -#else - return umul128(x, y).high(); -#endif -} - -// Computes upper 64 bits of multiplication of a 64-bit unsigned integer and a -// 128-bit unsigned integer. -inline uint64_t umul192_upper64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT { - uint128_wrapper g0 = umul128(x, y.high()); - g0 += umul128_upper64(x, y.low()); - return g0.high(); -} - -// Computes upper 32 bits of multiplication of a 32-bit unsigned integer and a +// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. -inline uint32_t umul96_upper32(uint32_t x, uint64_t y) FMT_NOEXCEPT { - return static_cast(umul128_upper64(x, y)); +inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t { + return umul128_upper64(static_cast(x) << 32, y); } -// Computes middle 64 bits of multiplication of a 64-bit unsigned integer and a +// Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. -inline uint64_t umul192_middle64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT { - uint64_t g01 = x * y.high(); - uint64_t g10 = umul128_upper64(x, y.low()); - return g01 + g10; +inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { + uint64_t high = x * y.high(); + uint128_fallback high_low = umul128(x, y.low()); + return {high + high_low.high(), high_low.low()}; } // Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. -inline uint64_t umul96_lower64(uint32_t x, uint64_t y) FMT_NOEXCEPT { +inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t { return x * y; } -// Computes floor(log10(pow(2, e))) for e in [-1700, 1700] using the method from -// https://fmt.dev/papers/Grisu-Exact.pdf#page=5, section 3.4. -inline int floor_log10_pow2(int e) FMT_NOEXCEPT { - FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); - const int shift = 22; - return (e * static_cast(log10_2_significand >> (64 - shift))) >> shift; -} - // Various fast log computations. -inline int floor_log2_pow10(int e) FMT_NOEXCEPT { - FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); - const uint64_t log2_10_integer_part = 3; - const uint64_t log2_10_fractional_digits = 0x5269e12f346e2bf9; - const int shift_amount = 19; - return (e * static_cast( - (log2_10_integer_part << shift_amount) | - (log2_10_fractional_digits >> (64 - shift_amount)))) >> - shift_amount; -} -inline int floor_log10_pow2_minus_log10_4_over_3(int e) FMT_NOEXCEPT { - FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); - const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375; - const int shift_amount = 22; - return (e * static_cast(log10_2_significand >> (64 - shift_amount)) - - static_cast(log10_4_over_3_fractional_digits >> - (64 - shift_amount))) >> - shift_amount; -} - -// Returns true iff x is divisible by pow(2, exp). -inline bool divisible_by_power_of_2(uint32_t x, int exp) FMT_NOEXCEPT { - FMT_ASSERT(exp >= 1, ""); - FMT_ASSERT(x != 0, ""); -#ifdef FMT_BUILTIN_CTZ - return FMT_BUILTIN_CTZ(x) >= exp; -#else - return exp < num_bits() && x == ((x >> exp) << exp); -#endif -} -inline bool divisible_by_power_of_2(uint64_t x, int exp) FMT_NOEXCEPT { - FMT_ASSERT(exp >= 1, ""); - FMT_ASSERT(x != 0, ""); -#ifdef FMT_BUILTIN_CTZLL - return FMT_BUILTIN_CTZLL(x) >= exp; -#else - return exp < num_bits() && x == ((x >> exp) << exp); -#endif +inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int { + FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); + return (e * 631305 - 261663) >> 21; } -// Table entry type for divisibility test. -template struct divtest_table_entry { - T mod_inv; - T max_quotient; -}; +FMT_INLINE_VARIABLE constexpr struct { + uint32_t divisor; + int shift_amount; +} div_small_pow10_infos[] = {{10, 16}, {100, 16}}; -// Returns true iff x is divisible by pow(5, exp). -inline bool divisible_by_power_of_5(uint32_t x, int exp) FMT_NOEXCEPT { - FMT_ASSERT(exp <= 10, "too large exponent"); - static constexpr const divtest_table_entry divtest_table[] = { - {0x00000001, 0xffffffff}, {0xcccccccd, 0x33333333}, - {0xc28f5c29, 0x0a3d70a3}, {0x26e978d5, 0x020c49ba}, - {0x3afb7e91, 0x0068db8b}, {0x0bcbe61d, 0x0014f8b5}, - {0x68c26139, 0x000431bd}, {0xae8d46a5, 0x0000d6bf}, - {0x22e90e21, 0x00002af3}, {0x3a2e9c6d, 0x00000897}, - {0x3ed61f49, 0x000001b7}}; - return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient; -} -inline bool divisible_by_power_of_5(uint64_t x, int exp) FMT_NOEXCEPT { - FMT_ASSERT(exp <= 23, "too large exponent"); - static constexpr const divtest_table_entry divtest_table[] = { - {0x0000000000000001, 0xffffffffffffffff}, - {0xcccccccccccccccd, 0x3333333333333333}, - {0x8f5c28f5c28f5c29, 0x0a3d70a3d70a3d70}, - {0x1cac083126e978d5, 0x020c49ba5e353f7c}, - {0xd288ce703afb7e91, 0x0068db8bac710cb2}, - {0x5d4e8fb00bcbe61d, 0x0014f8b588e368f0}, - {0x790fb65668c26139, 0x000431bde82d7b63}, - {0xe5032477ae8d46a5, 0x0000d6bf94d5e57a}, - {0xc767074b22e90e21, 0x00002af31dc46118}, - {0x8e47ce423a2e9c6d, 0x0000089705f4136b}, - {0x4fa7f60d3ed61f49, 0x000001b7cdfd9d7b}, - {0x0fee64690c913975, 0x00000057f5ff85e5}, - {0x3662e0e1cf503eb1, 0x000000119799812d}, - {0xa47a2cf9f6433fbd, 0x0000000384b84d09}, - {0x54186f653140a659, 0x00000000b424dc35}, - {0x7738164770402145, 0x0000000024075f3d}, - {0xe4a4d1417cd9a041, 0x000000000734aca5}, - {0xc75429d9e5c5200d, 0x000000000170ef54}, - {0xc1773b91fac10669, 0x000000000049c977}, - {0x26b172506559ce15, 0x00000000000ec1e4}, - {0xd489e3a9addec2d1, 0x000000000002f394}, - {0x90e860bb892c8d5d, 0x000000000000971d}, - {0x502e79bf1b6f4f79, 0x0000000000001e39}, - {0xdcd618596be30fe5, 0x000000000000060b}}; - return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient; -} - -// Replaces n by floor(n / pow(5, N)) returning true if and only if n is -// divisible by pow(5, N). -// Precondition: n <= 2 * pow(5, N + 1). +// Replaces n by floor(n / pow(10, N)) returning true if and only if n is +// divisible by pow(10, N). +// Precondition: n <= pow(10, N + 1). template -bool check_divisibility_and_divide_by_pow5(uint32_t& n) FMT_NOEXCEPT { - static constexpr struct { - uint32_t magic_number; - int bits_for_comparison; - uint32_t threshold; - int shift_amount; - } infos[] = {{0xcccd, 16, 0x3333, 18}, {0xa429, 8, 0x0a, 20}}; - constexpr auto info = infos[N - 1]; - n *= info.magic_number; - const uint32_t comparison_mask = (1u << info.bits_for_comparison) - 1; - bool result = (n & comparison_mask) <= info.threshold; +auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool { + // The numbers below are chosen such that: + // 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100, + // 2. nm mod 2^k < m if and only if n is divisible by d, + // where m is magic_number, k is shift_amount + // and d is divisor. + // + // Item 1 is a common technique of replacing division by a constant with + // multiplication, see e.g. "Division by Invariant Integers Using + // Multiplication" by Granlund and Montgomery (1994). magic_number (m) is set + // to ceil(2^k/d) for large enough k. + // The idea for item 2 originates from Schubfach. + constexpr auto info = div_small_pow10_infos[N - 1]; + FMT_ASSERT(n <= info.divisor * 10, "n is too large"); + constexpr uint32_t magic_number = + (1u << info.shift_amount) / info.divisor + 1; + n *= magic_number; + const uint32_t comparison_mask = (1u << info.shift_amount) - 1; + bool result = (n & comparison_mask) < magic_number; n >>= info.shift_amount; return result; } // Computes floor(n / pow(10, N)) for small n and N. // Precondition: n <= pow(10, N + 1). -template uint32_t small_division_by_pow10(uint32_t n) FMT_NOEXCEPT { - static constexpr struct { - uint32_t magic_number; - int shift_amount; - uint32_t divisor_times_10; - } infos[] = {{0xcccd, 19, 100}, {0xa3d8, 22, 1000}}; - constexpr auto info = infos[N - 1]; - FMT_ASSERT(n <= info.divisor_times_10, "n is too large"); - return n * info.magic_number >> info.shift_amount; +template auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t { + constexpr auto info = div_small_pow10_infos[N - 1]; + FMT_ASSERT(n <= info.divisor * 10, "n is too large"); + constexpr uint32_t magic_number = + (1u << info.shift_amount) / info.divisor + 1; + return (n * magic_number) >> info.shift_amount; } // Computes floor(n / 10^(kappa + 1)) (float) -inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) FMT_NOEXCEPT { - return n / float_info::big_divisor; +inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t { + // 1374389535 = ceil(2^37/100) + return static_cast((static_cast(n) * 1374389535) >> 37); } // Computes floor(n / 10^(kappa + 1)) (double) -inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) FMT_NOEXCEPT { - return umul128_upper64(n, 0x83126e978d4fdf3c) >> 9; +inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t { + // 2361183241434822607 = ceil(2^(64+7)/1000) + return umul128_upper64(n, 2361183241434822607ull) >> 7; } // Various subroutines using pow10 cache -template struct cache_accessor; +template struct cache_accessor; template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; using cache_entry_type = uint64_t; - static uint64_t get_cached_power(int k) FMT_NOEXCEPT { + static auto get_cached_power(int k) noexcept -> uint64_t { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); - constexpr const uint64_t pow10_significands[] = { + static constexpr const uint64_t pow10_significands[] = { 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, @@ -1091,54 +284,68 @@ template <> struct cache_accessor { 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, - 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940984, - 0xa18f07d736b90be5, 0xc9f2c9cd04674ede, 0xfc6f7c4045812296, - 0x9dc5ada82b70b59d, 0xc5371912364ce305, 0xf684df56c3e01bc6, - 0x9a130b963a6c115c, 0xc097ce7bc90715b3, 0xf0bdc21abb48db20, - 0x96769950b50d88f4, 0xbc143fa4e250eb31, 0xeb194f8e1ae525fd, - 0x92efd1b8d0cf37be, 0xb7abc627050305ad, 0xe596b7b0c643c719, - 0x8f7e32ce7bea5c6f, 0xb35dbf821ae4f38b, 0xe0352f62a19e306e}; + 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940985, + 0xa18f07d736b90be6, 0xc9f2c9cd04674edf, 0xfc6f7c4045812297, + 0x9dc5ada82b70b59e, 0xc5371912364ce306, 0xf684df56c3e01bc7, + 0x9a130b963a6c115d, 0xc097ce7bc90715b4, 0xf0bdc21abb48db21, + 0x96769950b50d88f5, 0xbc143fa4e250eb32, 0xeb194f8e1ae525fe, + 0x92efd1b8d0cf37bf, 0xb7abc627050305ae, 0xe596b7b0c643c71a, + 0x8f7e32ce7bea5c70, 0xb35dbf821ae4f38c, 0xe0352f62a19e306f}; return pow10_significands[k - float_info::min_k]; } - static carrier_uint compute_mul(carrier_uint u, - const cache_entry_type& cache) FMT_NOEXCEPT { - return umul96_upper32(u, cache); + struct compute_mul_result { + carrier_uint result; + bool is_integer; + }; + struct compute_mul_parity_result { + bool parity; + bool is_integer; + }; + + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { + auto r = umul96_upper64(u, cache); + return {static_cast(r >> 32), + static_cast(r) == 0}; } - static uint32_t compute_delta(const cache_entry_type& cache, - int beta_minus_1) FMT_NOEXCEPT { - return static_cast(cache >> (64 - 1 - beta_minus_1)); + static auto compute_delta(const cache_entry_type& cache, int beta) noexcept + -> uint32_t { + return static_cast(cache >> (64 - 1 - beta)); } - static bool compute_mul_parity(carrier_uint two_f, + static auto compute_mul_parity(carrier_uint two_f, const cache_entry_type& cache, - int beta_minus_1) FMT_NOEXCEPT { - FMT_ASSERT(beta_minus_1 >= 1, ""); - FMT_ASSERT(beta_minus_1 < 64, ""); + int beta) noexcept + -> compute_mul_parity_result { + FMT_ASSERT(beta >= 1, ""); + FMT_ASSERT(beta < 64, ""); - return ((umul96_lower64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0; + auto r = umul96_lower64(two_f, cache); + return {((r >> (64 - beta)) & 1) != 0, + static_cast(r >> (32 - beta)) == 0}; } - static carrier_uint compute_left_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast( - (cache - (cache >> (float_info::significand_bits + 2))) >> - (64 - float_info::significand_bits - 1 - beta_minus_1)); + (cache - (cache >> (num_significand_bits() + 2))) >> + (64 - num_significand_bits() - 1 - beta)); } - static carrier_uint compute_right_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast( - (cache + (cache >> (float_info::significand_bits + 1))) >> - (64 - float_info::significand_bits - 1 - beta_minus_1)); + (cache + (cache >> (num_significand_bits() + 1))) >> + (64 - num_significand_bits() - 1 - beta)); } - static carrier_uint compute_round_up_for_shorter_interval_case( - const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (static_cast( - cache >> - (64 - float_info::significand_bits - 2 - beta_minus_1)) + + cache >> (64 - num_significand_bits() - 2 - beta)) + 1) / 2; } @@ -1146,13 +353,13 @@ template <> struct cache_accessor { template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; - using cache_entry_type = uint128_wrapper; + using cache_entry_type = uint128_fallback; - static uint128_wrapper get_cached_power(int k) FMT_NOEXCEPT { + static auto get_cached_power(int k) noexcept -> uint128_fallback { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); - static constexpr const uint128_wrapper pow10_significands[] = { + static constexpr const uint128_fallback pow10_significands[] = { #if FMT_USE_FULL_CACHE_DRAGONBOX {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0x9faacf3df73609b1, 0x77b191618c54e9ad}, @@ -1502,278 +709,292 @@ template <> struct cache_accessor { {0x85a36366eb71f041, 0x47a6da2b7f864750}, {0xa70c3c40a64e6c51, 0x999090b65f67d924}, {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, - {0x82818f1281ed449f, 0xbff8f10e7a8921a4}, - {0xa321f2d7226895c7, 0xaff72d52192b6a0d}, - {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490}, - {0xfee50b7025c36a08, 0x02f236d04753d5b4}, - {0x9f4f2726179a2245, 0x01d762422c946590}, - {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5}, - {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2}, - {0x9b934c3b330c8577, 0x63cc55f49f88eb2f}, - {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb}, - {0xf316271c7fc3908a, 0x8bef464e3945ef7a}, - {0x97edd871cfda3a56, 0x97758bf0e3cbb5ac}, - {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317}, - {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd}, - {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a}, - {0xb975d6b6ee39e436, 0xb3e2fd538e122b44}, - {0xe7d34c64a9c85d44, 0x60dbbca87196b616}, - {0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd}, - {0xb51d13aea4a488dd, 0x6babab6398bdbe41}, - {0xe264589a4dcdab14, 0xc696963c7eed2dd1}, - {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2}, - {0xb0de65388cc8ada8, 0x3b25a55f43294bcb}, - {0xdd15fe86affad912, 0x49ef0eb713f39ebe}, - {0x8a2dbf142dfcc7ab, 0x6e3569326c784337}, - {0xacb92ed9397bf996, 0x49c2c37f07965404}, - {0xd7e77a8f87daf7fb, 0xdc33745ec97be906}, - {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3}, - {0xa8acd7c0222311bc, 0xc40832ea0d68ce0c}, - {0xd2d80db02aabd62b, 0xf50a3fa490c30190}, - {0x83c7088e1aab65db, 0x792667c6da79e0fa}, - {0xa4b8cab1a1563f52, 0x577001b891185938}, - {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86}, - {0x80b05e5ac60b6178, 0x544f8158315b05b4}, - {0xa0dc75f1778e39d6, 0x696361ae3db1c721}, - {0xc913936dd571c84c, 0x03bc3a19cd1e38e9}, - {0xfb5878494ace3a5f, 0x04ab48a04065c723}, - {0x9d174b2dcec0e47b, 0x62eb0d64283f9c76}, - {0xc45d1df942711d9a, 0x3ba5d0bd324f8394}, - {0xf5746577930d6500, 0xca8f44ec7ee36479}, - {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb}, - {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e}, - {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e}, - {0x95d04aee3b80ece5, 0xbba1f1d158724a12}, - {0xbb445da9ca61281f, 0x2a8a6e45ae8edc97}, - {0xea1575143cf97226, 0xf52d09d71a3293bd}, - {0x924d692ca61be758, 0x593c2626705f9c56}, - {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c}, - {0xe498f455c38b997a, 0x0b6dfb9c0f956447}, - {0x8edf98b59a373fec, 0x4724bd4189bd5eac}, - {0xb2977ee300c50fe7, 0x58edec91ec2cb657}, - {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed}, - {0x8b865b215899f46c, 0xbd79e0d20082ee74}, - {0xae67f1e9aec07187, 0xecd8590680a3aa11}, - {0xda01ee641a708de9, 0xe80e6f4820cc9495}, - {0x884134fe908658b2, 0x3109058d147fdcdd}, - {0xaa51823e34a7eede, 0xbd4b46f0599fd415}, - {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a}, - {0x850fadc09923329e, 0x03e2cf6bc604ddb0}, - {0xa6539930bf6bff45, 0x84db8346b786151c}, - {0xcfe87f7cef46ff16, 0xe612641865679a63}, - {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e}, - {0xa26da3999aef7749, 0xe3be5e330f38f09d}, - {0xcb090c8001ab551c, 0x5cadf5bfd3072cc5}, - {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6}, - {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa}, - {0xc646d63501a1511d, 0xb281e1fd541501b8}, - {0xf7d88bc24209a565, 0x1f225a7ca91a4226}, - {0x9ae757596946075f, 0x3375788de9b06958}, - {0xc1a12d2fc3978937, 0x0052d6b1641c83ae}, - {0xf209787bb47d6b84, 0xc0678c5dbd23a49a}, - {0x9745eb4d50ce6332, 0xf840b7ba963646e0}, - {0xbd176620a501fbff, 0xb650e5a93bc3d898}, - {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe}, - {0x93ba47c980e98cdf, 0xc66f336c36b10137}, - {0xb8a8d9bbe123f017, 0xb80b0047445d4184}, - {0xe6d3102ad96cec1d, 0xa60dc059157491e5}, - {0x9043ea1ac7e41392, 0x87c89837ad68db2f}, - {0xb454e4a179dd1877, 0x29babe4598c311fb}, - {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a}, - {0x8ce2529e2734bb1d, 0x1899e4a65f58660c}, - {0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f}, - {0xdc21a1171d42645d, 0x76707543f4fa1f73}, - {0x899504ae72497eba, 0x6a06494a791c53a8}, - {0xabfa45da0edbde69, 0x0487db9d17636892}, - {0xd6f8d7509292d603, 0x45a9d2845d3c42b6}, - {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2}, - {0xa7f26836f282b732, 0x8e6cac7768d7141e}, - {0xd1ef0244af2364ff, 0x3207d795430cd926}, - {0x8335616aed761f1f, 0x7f44e6bd49e807b8}, - {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6}, - {0xcd036837130890a1, 0x36dba887c37a8c0f}, - {0x802221226be55a64, 0xc2494954da2c9789}, - {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c}, - {0xc83553c5c8965d3d, 0x6f92829494e5acc7}, - {0xfa42a8b73abbf48c, 0xcb772339ba1f17f9}, - {0x9c69a97284b578d7, 0xff2a760414536efb}, - {0xc38413cf25e2d70d, 0xfef5138519684aba}, - {0xf46518c2ef5b8cd1, 0x7eb258665fc25d69}, - {0x98bf2f79d5993802, 0xef2f773ffbd97a61}, - {0xbeeefb584aff8603, 0xaafb550ffacfd8fa}, - {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38}, - {0x952ab45cfa97a0b2, 0xdd945a747bf26183}, - {0xba756174393d88df, 0x94f971119aeef9e4}, - {0xe912b9d1478ceb17, 0x7a37cd5601aab85d}, - {0x91abb422ccb812ee, 0xac62e055c10ab33a}, - {0xb616a12b7fe617aa, 0x577b986b314d6009}, - {0xe39c49765fdf9d94, 0xed5a7e85fda0b80b}, - {0x8e41ade9fbebc27d, 0x14588f13be847307}, - {0xb1d219647ae6b31c, 0x596eb2d8ae258fc8}, - {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb}, - {0x8aec23d680043bee, 0x25de7bb9480d5854}, - {0xada72ccc20054ae9, 0xaf561aa79a10ae6a}, - {0xd910f7ff28069da4, 0x1b2ba1518094da04}, - {0x87aa9aff79042286, 0x90fb44d2f05d0842}, - {0xa99541bf57452b28, 0x353a1607ac744a53}, - {0xd3fa922f2d1675f2, 0x42889b8997915ce8}, - {0x847c9b5d7c2e09b7, 0x69956135febada11}, - {0xa59bc234db398c25, 0x43fab9837e699095}, - {0xcf02b2c21207ef2e, 0x94f967e45e03f4bb}, - {0x8161afb94b44f57d, 0x1d1be0eebac278f5}, - {0xa1ba1ba79e1632dc, 0x6462d92a69731732}, - {0xca28a291859bbf93, 0x7d7b8f7503cfdcfe}, - {0xfcb2cb35e702af78, 0x5cda735244c3d43e}, - {0x9defbf01b061adab, 0x3a0888136afa64a7}, - {0xc56baec21c7a1916, 0x088aaa1845b8fdd0}, - {0xf6c69a72a3989f5b, 0x8aad549e57273d45}, - {0x9a3c2087a63f6399, 0x36ac54e2f678864b}, - {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd}, - {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5}, - {0x969eb7c47859e743, 0x9f644ae5a4b1b325}, - {0xbc4665b596706114, 0x873d5d9f0dde1fee}, - {0xeb57ff22fc0c7959, 0xa90cb506d155a7ea}, - {0x9316ff75dd87cbd8, 0x09a7f12442d588f2}, - {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb2f}, - {0xe5d3ef282a242e81, 0x8f1668c8a86da5fa}, - {0x8fa475791a569d10, 0xf96e017d694487bc}, - {0xb38d92d760ec4455, 0x37c981dcc395a9ac}, - {0xe070f78d3927556a, 0x85bbe253f47b1417}, - {0x8c469ab843b89562, 0x93956d7478ccec8e}, - {0xaf58416654a6babb, 0x387ac8d1970027b2}, - {0xdb2e51bfe9d0696a, 0x06997b05fcc0319e}, - {0x88fcf317f22241e2, 0x441fece3bdf81f03}, - {0xab3c2fddeeaad25a, 0xd527e81cad7626c3}, - {0xd60b3bd56a5586f1, 0x8a71e223d8d3b074}, - {0x85c7056562757456, 0xf6872d5667844e49}, - {0xa738c6bebb12d16c, 0xb428f8ac016561db}, - {0xd106f86e69d785c7, 0xe13336d701beba52}, - {0x82a45b450226b39c, 0xecc0024661173473}, - {0xa34d721642b06084, 0x27f002d7f95d0190}, - {0xcc20ce9bd35c78a5, 0x31ec038df7b441f4}, - {0xff290242c83396ce, 0x7e67047175a15271}, - {0x9f79a169bd203e41, 0x0f0062c6e984d386}, - {0xc75809c42c684dd1, 0x52c07b78a3e60868}, - {0xf92e0c3537826145, 0xa7709a56ccdf8a82}, - {0x9bbcc7a142b17ccb, 0x88a66076400bb691}, - {0xc2abf989935ddbfe, 0x6acff893d00ea435}, - {0xf356f7ebf83552fe, 0x0583f6b8c4124d43}, - {0x98165af37b2153de, 0xc3727a337a8b704a}, - {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c}, - {0xeda2ee1c7064130c, 0x1162def06f79df73}, - {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8}, - {0xb9a74a0637ce2ee1, 0x6d953e2bd7173692}, - {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437}, - {0x910ab1d4db9914a0, 0x1d9c9892400a22a2}, - {0xb54d5e4a127f59c8, 0x2503beb6d00cab4b}, - {0xe2a0b5dc971f303a, 0x2e44ae64840fd61d}, - {0x8da471a9de737e24, 0x5ceaecfed289e5d2}, - {0xb10d8e1456105dad, 0x7425a83e872c5f47}, - {0xdd50f1996b947518, 0xd12f124e28f77719}, - {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f}, - {0xace73cbfdc0bfb7b, 0x636cc64d1001550b}, - {0xd8210befd30efa5a, 0x3c47f7e05401aa4e}, - {0x8714a775e3e95c78, 0x65acfaec34810a71}, - {0xa8d9d1535ce3b396, 0x7f1839a741a14d0d}, - {0xd31045a8341ca07c, 0x1ede48111209a050}, - {0x83ea2b892091e44d, 0x934aed0aab460432}, - {0xa4e4b66b68b65d60, 0xf81da84d5617853f}, - {0xce1de40642e3f4b9, 0x36251260ab9d668e}, - {0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019}, - {0xa1075a24e4421730, 0xb24cf65b8612f81f}, - {0xc94930ae1d529cfc, 0xdee033f26797b627}, - {0xfb9b7cd9a4a7443c, 0x169840ef017da3b1}, - {0x9d412e0806e88aa5, 0x8e1f289560ee864e}, - {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2}, - {0xf5b5d7ec8acb58a2, 0xae10af696774b1db}, - {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29}, - {0xbff610b0cc6edd3f, 0x17fd090a58d32af3}, - {0xeff394dcff8a948e, 0xddfc4b4cef07f5b0}, - {0x95f83d0a1fb69cd9, 0x4abdaf101564f98e}, - {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1}, - {0xea53df5fd18d5513, 0x84c86189216dc5ed}, - {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4}, - {0xb7118682dbb66a77, 0x3fbc8c33221dc2a1}, - {0xe4d5e82392a40515, 0x0fabaf3feaa5334a}, - {0x8f05b1163ba6832d, 0x29cb4d87f2a7400e}, - {0xb2c71d5bca9023f8, 0x743e20e9ef511012}, - {0xdf78e4b2bd342cf6, 0x914da9246b255416}, - {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e}, - {0xae9672aba3d0c320, 0xa184ac2473b529b1}, - {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e}, - {0x8865899617fb1871, 0x7e2fa67c7a658892}, - {0xaa7eebfb9df9de8d, 0xddbb901b98feeab7}, - {0xd51ea6fa85785631, 0x552a74227f3ea565}, - {0x8533285c936b35de, 0xd53a88958f87275f}, - {0xa67ff273b8460356, 0x8a892abaf368f137}, - {0xd01fef10a657842c, 0x2d2b7569b0432d85}, - {0x8213f56a67f6b29b, 0x9c3b29620e29fc73}, - {0xa298f2c501f45f42, 0x8349f3ba91b47b8f}, - {0xcb3f2f7642717713, 0x241c70a936219a73}, - {0xfe0efb53d30dd4d7, 0xed238cd383aa0110}, - {0x9ec95d1463e8a506, 0xf4363804324a40aa}, - {0xc67bb4597ce2ce48, 0xb143c6053edcd0d5}, - {0xf81aa16fdc1b81da, 0xdd94b7868e94050a}, - {0x9b10a4e5e9913128, 0xca7cf2b4191c8326}, - {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0}, - {0xf24a01a73cf2dccf, 0xbc633b39673c8cec}, - {0x976e41088617ca01, 0xd5be0503e085d813}, - {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18}, - {0xec9c459d51852ba2, 0xddf8e7d60ed1219e}, - {0x93e1ab8252f33b45, 0xcabb90e5c942b503}, - {0xb8da1662e7b00a17, 0x3d6a751f3b936243}, - {0xe7109bfba19c0c9d, 0x0cc512670a783ad4}, - {0x906a617d450187e2, 0x27fb2b80668b24c5}, - {0xb484f9dc9641e9da, 0xb1f9f660802dedf6}, - {0xe1a63853bbd26451, 0x5e7873f8a0396973}, - {0x8d07e33455637eb2, 0xdb0b487b6423e1e8}, - {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62}, - {0xdc5c5301c56b75f7, 0x7641a140cc7810fb}, - {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d}, - {0xac2820d9623bf429, 0x546345fa9fbdcd44}, - {0xd732290fbacaf133, 0xa97c177947ad4095}, - {0x867f59a9d4bed6c0, 0x49ed8eabcccc485d}, - {0xa81f301449ee8c70, 0x5c68f256bfff5a74}, - {0xd226fc195c6a2f8c, 0x73832eec6fff3111}, - {0x83585d8fd9c25db7, 0xc831fd53c5ff7eab}, - {0xa42e74f3d032f525, 0xba3e7ca8b77f5e55}, - {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb}, - {0x80444b5e7aa7cf85, 0x7980d163cf5b81b3}, - {0xa0555e361951c366, 0xd7e105bcc332621f}, - {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7}, - {0xfa856334878fc150, 0xb14f98f6f0feb951}, - {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3}, - {0xc3b8358109e84f07, 0x0a862f80ec4700c8}, - {0xf4a642e14c6262c8, 0xcd27bb612758c0fa}, - {0x98e7e9cccfbd7dbd, 0x8038d51cb897789c}, - {0xbf21e44003acdd2c, 0xe0470a63e6bd56c3}, - {0xeeea5d5004981478, 0x1858ccfce06cac74}, - {0x95527a5202df0ccb, 0x0f37801e0c43ebc8}, - {0xbaa718e68396cffd, 0xd30560258f54e6ba}, - {0xe950df20247c83fd, 0x47c6b82ef32a2069}, - {0x91d28b7416cdd27e, 0x4cdc331d57fa5441}, - {0xb6472e511c81471d, 0xe0133fe4adf8e952}, - {0xe3d8f9e563a198e5, 0x58180fddd97723a6}, - {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648}, - {0xb201833b35d63f73, 0x2cd2cc6551e513da}, - {0xde81e40a034bcf4f, 0xf8077f7ea65e58d1}, - {0x8b112e86420f6191, 0xfb04afaf27faf782}, - {0xadd57a27d29339f6, 0x79c5db9af1f9b563}, - {0xd94ad8b1c7380874, 0x18375281ae7822bc}, - {0x87cec76f1c830548, 0x8f2293910d0b15b5}, - {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb22}, - {0xd433179d9c8cb841, 0x5fa60692a46151eb}, - {0x849feec281d7f328, 0xdbc7c41ba6bcd333}, - {0xa5c7ea73224deff3, 0x12b9b522906c0800}, - {0xcf39e50feae16bef, 0xd768226b34870a00}, - {0x81842f29f2cce375, 0xe6a1158300d46640}, - {0xa1e53af46f801c53, 0x60495ae3c1097fd0}, - {0xca5e89b18b602368, 0x385bb19cb14bdfc4}, - {0xfcf62c1dee382c42, 0x46729e03dd9ed7b5}, - {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d1}, - {0xc5a05277621be293, 0xc7098b7305241885}, - { 0xf70867153aa2db38, - 0xb8cbee4fc66d1ea7 } + {0x82818f1281ed449f, 0xbff8f10e7a8921a5}, + {0xa321f2d7226895c7, 0xaff72d52192b6a0e}, + {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764491}, + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0x9f4f2726179a2245, 0x01d762422c946591}, + {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef6}, + {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb3}, + {0x9b934c3b330c8577, 0x63cc55f49f88eb30}, + {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fc}, + {0xf316271c7fc3908a, 0x8bef464e3945ef7b}, + {0x97edd871cfda3a56, 0x97758bf0e3cbb5ad}, + {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea318}, + {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bde}, + {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6b}, + {0xb975d6b6ee39e436, 0xb3e2fd538e122b45}, + {0xe7d34c64a9c85d44, 0x60dbbca87196b617}, + {0x90e40fbeea1d3a4a, 0xbc8955e946fe31ce}, + {0xb51d13aea4a488dd, 0x6babab6398bdbe42}, + {0xe264589a4dcdab14, 0xc696963c7eed2dd2}, + {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca3}, + {0xb0de65388cc8ada8, 0x3b25a55f43294bcc}, + {0xdd15fe86affad912, 0x49ef0eb713f39ebf}, + {0x8a2dbf142dfcc7ab, 0x6e3569326c784338}, + {0xacb92ed9397bf996, 0x49c2c37f07965405}, + {0xd7e77a8f87daf7fb, 0xdc33745ec97be907}, + {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a4}, + {0xa8acd7c0222311bc, 0xc40832ea0d68ce0d}, + {0xd2d80db02aabd62b, 0xf50a3fa490c30191}, + {0x83c7088e1aab65db, 0x792667c6da79e0fb}, + {0xa4b8cab1a1563f52, 0x577001b891185939}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0x80b05e5ac60b6178, 0x544f8158315b05b5}, + {0xa0dc75f1778e39d6, 0x696361ae3db1c722}, + {0xc913936dd571c84c, 0x03bc3a19cd1e38ea}, + {0xfb5878494ace3a5f, 0x04ab48a04065c724}, + {0x9d174b2dcec0e47b, 0x62eb0d64283f9c77}, + {0xc45d1df942711d9a, 0x3ba5d0bd324f8395}, + {0xf5746577930d6500, 0xca8f44ec7ee3647a}, + {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecc}, + {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67f}, + {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101f}, + {0x95d04aee3b80ece5, 0xbba1f1d158724a13}, + {0xbb445da9ca61281f, 0x2a8a6e45ae8edc98}, + {0xea1575143cf97226, 0xf52d09d71a3293be}, + {0x924d692ca61be758, 0x593c2626705f9c57}, + {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836d}, + {0xe498f455c38b997a, 0x0b6dfb9c0f956448}, + {0x8edf98b59a373fec, 0x4724bd4189bd5ead}, + {0xb2977ee300c50fe7, 0x58edec91ec2cb658}, + {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ee}, + {0x8b865b215899f46c, 0xbd79e0d20082ee75}, + {0xae67f1e9aec07187, 0xecd8590680a3aa12}, + {0xda01ee641a708de9, 0xe80e6f4820cc9496}, + {0x884134fe908658b2, 0x3109058d147fdcde}, + {0xaa51823e34a7eede, 0xbd4b46f0599fd416}, + {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91b}, + {0x850fadc09923329e, 0x03e2cf6bc604ddb1}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0xcfe87f7cef46ff16, 0xe612641865679a64}, + {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07f}, + {0xa26da3999aef7749, 0xe3be5e330f38f09e}, + {0xcb090c8001ab551c, 0x5cadf5bfd3072cc6}, + {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f7}, + {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afb}, + {0xc646d63501a1511d, 0xb281e1fd541501b9}, + {0xf7d88bc24209a565, 0x1f225a7ca91a4227}, + {0x9ae757596946075f, 0x3375788de9b06959}, + {0xc1a12d2fc3978937, 0x0052d6b1641c83af}, + {0xf209787bb47d6b84, 0xc0678c5dbd23a49b}, + {0x9745eb4d50ce6332, 0xf840b7ba963646e1}, + {0xbd176620a501fbff, 0xb650e5a93bc3d899}, + {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebf}, + {0x93ba47c980e98cdf, 0xc66f336c36b10138}, + {0xb8a8d9bbe123f017, 0xb80b0047445d4185}, + {0xe6d3102ad96cec1d, 0xa60dc059157491e6}, + {0x9043ea1ac7e41392, 0x87c89837ad68db30}, + {0xb454e4a179dd1877, 0x29babe4598c311fc}, + {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67b}, + {0x8ce2529e2734bb1d, 0x1899e4a65f58660d}, + {0xb01ae745b101e9e4, 0x5ec05dcff72e7f90}, + {0xdc21a1171d42645d, 0x76707543f4fa1f74}, + {0x899504ae72497eba, 0x6a06494a791c53a9}, + {0xabfa45da0edbde69, 0x0487db9d17636893}, + {0xd6f8d7509292d603, 0x45a9d2845d3c42b7}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xa7f26836f282b732, 0x8e6cac7768d7141f}, + {0xd1ef0244af2364ff, 0x3207d795430cd927}, + {0x8335616aed761f1f, 0x7f44e6bd49e807b9}, + {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a7}, + {0xcd036837130890a1, 0x36dba887c37a8c10}, + {0x802221226be55a64, 0xc2494954da2c978a}, + {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6d}, + {0xc83553c5c8965d3d, 0x6f92829494e5acc8}, + {0xfa42a8b73abbf48c, 0xcb772339ba1f17fa}, + {0x9c69a97284b578d7, 0xff2a760414536efc}, + {0xc38413cf25e2d70d, 0xfef5138519684abb}, + {0xf46518c2ef5b8cd1, 0x7eb258665fc25d6a}, + {0x98bf2f79d5993802, 0xef2f773ffbd97a62}, + {0xbeeefb584aff8603, 0xaafb550ffacfd8fb}, + {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf39}, + {0x952ab45cfa97a0b2, 0xdd945a747bf26184}, + {0xba756174393d88df, 0x94f971119aeef9e5}, + {0xe912b9d1478ceb17, 0x7a37cd5601aab85e}, + {0x91abb422ccb812ee, 0xac62e055c10ab33b}, + {0xb616a12b7fe617aa, 0x577b986b314d600a}, + {0xe39c49765fdf9d94, 0xed5a7e85fda0b80c}, + {0x8e41ade9fbebc27d, 0x14588f13be847308}, + {0xb1d219647ae6b31c, 0x596eb2d8ae258fc9}, + {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bc}, + {0x8aec23d680043bee, 0x25de7bb9480d5855}, + {0xada72ccc20054ae9, 0xaf561aa79a10ae6b}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0x87aa9aff79042286, 0x90fb44d2f05d0843}, + {0xa99541bf57452b28, 0x353a1607ac744a54}, + {0xd3fa922f2d1675f2, 0x42889b8997915ce9}, + {0x847c9b5d7c2e09b7, 0x69956135febada12}, + {0xa59bc234db398c25, 0x43fab9837e699096}, + {0xcf02b2c21207ef2e, 0x94f967e45e03f4bc}, + {0x8161afb94b44f57d, 0x1d1be0eebac278f6}, + {0xa1ba1ba79e1632dc, 0x6462d92a69731733}, + {0xca28a291859bbf93, 0x7d7b8f7503cfdcff}, + {0xfcb2cb35e702af78, 0x5cda735244c3d43f}, + {0x9defbf01b061adab, 0x3a0888136afa64a8}, + {0xc56baec21c7a1916, 0x088aaa1845b8fdd1}, + {0xf6c69a72a3989f5b, 0x8aad549e57273d46}, + {0x9a3c2087a63f6399, 0x36ac54e2f678864c}, + {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7de}, + {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d6}, + {0x969eb7c47859e743, 0x9f644ae5a4b1b326}, + {0xbc4665b596706114, 0x873d5d9f0dde1fef}, + {0xeb57ff22fc0c7959, 0xa90cb506d155a7eb}, + {0x9316ff75dd87cbd8, 0x09a7f12442d588f3}, + {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30}, + {0xe5d3ef282a242e81, 0x8f1668c8a86da5fb}, + {0x8fa475791a569d10, 0xf96e017d694487bd}, + {0xb38d92d760ec4455, 0x37c981dcc395a9ad}, + {0xe070f78d3927556a, 0x85bbe253f47b1418}, + {0x8c469ab843b89562, 0x93956d7478ccec8f}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0xdb2e51bfe9d0696a, 0x06997b05fcc0319f}, + {0x88fcf317f22241e2, 0x441fece3bdf81f04}, + {0xab3c2fddeeaad25a, 0xd527e81cad7626c4}, + {0xd60b3bd56a5586f1, 0x8a71e223d8d3b075}, + {0x85c7056562757456, 0xf6872d5667844e4a}, + {0xa738c6bebb12d16c, 0xb428f8ac016561dc}, + {0xd106f86e69d785c7, 0xe13336d701beba53}, + {0x82a45b450226b39c, 0xecc0024661173474}, + {0xa34d721642b06084, 0x27f002d7f95d0191}, + {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5}, + {0xff290242c83396ce, 0x7e67047175a15272}, + {0x9f79a169bd203e41, 0x0f0062c6e984d387}, + {0xc75809c42c684dd1, 0x52c07b78a3e60869}, + {0xf92e0c3537826145, 0xa7709a56ccdf8a83}, + {0x9bbcc7a142b17ccb, 0x88a66076400bb692}, + {0xc2abf989935ddbfe, 0x6acff893d00ea436}, + {0xf356f7ebf83552fe, 0x0583f6b8c4124d44}, + {0x98165af37b2153de, 0xc3727a337a8b704b}, + {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5d}, + {0xeda2ee1c7064130c, 0x1162def06f79df74}, + {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba9}, + {0xb9a74a0637ce2ee1, 0x6d953e2bd7173693}, + {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0438}, + {0x910ab1d4db9914a0, 0x1d9c9892400a22a3}, + {0xb54d5e4a127f59c8, 0x2503beb6d00cab4c}, + {0xe2a0b5dc971f303a, 0x2e44ae64840fd61e}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xb10d8e1456105dad, 0x7425a83e872c5f48}, + {0xdd50f1996b947518, 0xd12f124e28f7771a}, + {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa70}, + {0xace73cbfdc0bfb7b, 0x636cc64d1001550c}, + {0xd8210befd30efa5a, 0x3c47f7e05401aa4f}, + {0x8714a775e3e95c78, 0x65acfaec34810a72}, + {0xa8d9d1535ce3b396, 0x7f1839a741a14d0e}, + {0xd31045a8341ca07c, 0x1ede48111209a051}, + {0x83ea2b892091e44d, 0x934aed0aab460433}, + {0xa4e4b66b68b65d60, 0xf81da84d56178540}, + {0xce1de40642e3f4b9, 0x36251260ab9d668f}, + {0x80d2ae83e9ce78f3, 0xc1d72b7c6b42601a}, + {0xa1075a24e4421730, 0xb24cf65b8612f820}, + {0xc94930ae1d529cfc, 0xdee033f26797b628}, + {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2}, + {0x9d412e0806e88aa5, 0x8e1f289560ee864f}, + {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e3}, + {0xf5b5d7ec8acb58a2, 0xae10af696774b1dc}, + {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef2a}, + {0xbff610b0cc6edd3f, 0x17fd090a58d32af4}, + {0xeff394dcff8a948e, 0xddfc4b4cef07f5b1}, + {0x95f83d0a1fb69cd9, 0x4abdaf101564f98f}, + {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f2}, + {0xea53df5fd18d5513, 0x84c86189216dc5ee}, + {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb5}, + {0xb7118682dbb66a77, 0x3fbc8c33221dc2a2}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0x8f05b1163ba6832d, 0x29cb4d87f2a7400f}, + {0xb2c71d5bca9023f8, 0x743e20e9ef511013}, + {0xdf78e4b2bd342cf6, 0x914da9246b255417}, + {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f}, + {0xae9672aba3d0c320, 0xa184ac2473b529b2}, + {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741f}, + {0x8865899617fb1871, 0x7e2fa67c7a658893}, + {0xaa7eebfb9df9de8d, 0xddbb901b98feeab8}, + {0xd51ea6fa85785631, 0x552a74227f3ea566}, + {0x8533285c936b35de, 0xd53a88958f872760}, + {0xa67ff273b8460356, 0x8a892abaf368f138}, + {0xd01fef10a657842c, 0x2d2b7569b0432d86}, + {0x8213f56a67f6b29b, 0x9c3b29620e29fc74}, + {0xa298f2c501f45f42, 0x8349f3ba91b47b90}, + {0xcb3f2f7642717713, 0x241c70a936219a74}, + {0xfe0efb53d30dd4d7, 0xed238cd383aa0111}, + {0x9ec95d1463e8a506, 0xf4363804324a40ab}, + {0xc67bb4597ce2ce48, 0xb143c6053edcd0d6}, + {0xf81aa16fdc1b81da, 0xdd94b7868e94050b}, + {0x9b10a4e5e9913128, 0xca7cf2b4191c8327}, + {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f1}, + {0xf24a01a73cf2dccf, 0xbc633b39673c8ced}, + {0x976e41088617ca01, 0xd5be0503e085d814}, + {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e19}, + {0xec9c459d51852ba2, 0xddf8e7d60ed1219f}, + {0x93e1ab8252f33b45, 0xcabb90e5c942b504}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0xe7109bfba19c0c9d, 0x0cc512670a783ad5}, + {0x906a617d450187e2, 0x27fb2b80668b24c6}, + {0xb484f9dc9641e9da, 0xb1f9f660802dedf7}, + {0xe1a63853bbd26451, 0x5e7873f8a0396974}, + {0x8d07e33455637eb2, 0xdb0b487b6423e1e9}, + {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda63}, + {0xdc5c5301c56b75f7, 0x7641a140cc7810fc}, + {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9e}, + {0xac2820d9623bf429, 0x546345fa9fbdcd45}, + {0xd732290fbacaf133, 0xa97c177947ad4096}, + {0x867f59a9d4bed6c0, 0x49ed8eabcccc485e}, + {0xa81f301449ee8c70, 0x5c68f256bfff5a75}, + {0xd226fc195c6a2f8c, 0x73832eec6fff3112}, + {0x83585d8fd9c25db7, 0xc831fd53c5ff7eac}, + {0xa42e74f3d032f525, 0xba3e7ca8b77f5e56}, + {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35ec}, + {0x80444b5e7aa7cf85, 0x7980d163cf5b81b4}, + {0xa0555e361951c366, 0xd7e105bcc3326220}, + {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa8}, + {0xfa856334878fc150, 0xb14f98f6f0feb952}, + {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d4}, + {0xc3b8358109e84f07, 0x0a862f80ec4700c9}, + {0xf4a642e14c6262c8, 0xcd27bb612758c0fb}, + {0x98e7e9cccfbd7dbd, 0x8038d51cb897789d}, + {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4}, + {0xeeea5d5004981478, 0x1858ccfce06cac75}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xbaa718e68396cffd, 0xd30560258f54e6bb}, + {0xe950df20247c83fd, 0x47c6b82ef32a206a}, + {0x91d28b7416cdd27e, 0x4cdc331d57fa5442}, + {0xb6472e511c81471d, 0xe0133fe4adf8e953}, + {0xe3d8f9e563a198e5, 0x58180fddd97723a7}, + {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7649}, + {0xb201833b35d63f73, 0x2cd2cc6551e513db}, + {0xde81e40a034bcf4f, 0xf8077f7ea65e58d2}, + {0x8b112e86420f6191, 0xfb04afaf27faf783}, + {0xadd57a27d29339f6, 0x79c5db9af1f9b564}, + {0xd94ad8b1c7380874, 0x18375281ae7822bd}, + {0x87cec76f1c830548, 0x8f2293910d0b15b6}, + {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb23}, + {0xd433179d9c8cb841, 0x5fa60692a46151ec}, + {0x849feec281d7f328, 0xdbc7c41ba6bcd334}, + {0xa5c7ea73224deff3, 0x12b9b522906c0801}, + {0xcf39e50feae16bef, 0xd768226b34870a01}, + {0x81842f29f2cce375, 0xe6a1158300d46641}, + {0xa1e53af46f801c53, 0x60495ae3c1097fd1}, + {0xca5e89b18b602368, 0x385bb19cb14bdfc5}, + {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, + {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, + {0xc5a05277621be293, 0xc7098b7305241886}, + {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, + {0x9a65406d44a5c903, 0x737f74f1dc043329}, + {0xc0fe908895cf3b44, 0x505f522e53053ff3}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, + {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, + {0xbc789925624c5fe0, 0xb67d16413d132073}, + {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, + {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, + {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, + {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, + {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, + {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, + {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, + {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, + {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, + {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, #else {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, @@ -1788,17 +1009,17 @@ template <> struct cache_accessor { {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, {0xc350000000000000, 0x0000000000000000}, {0x9dc5ada82b70b59d, 0xf020000000000000}, - {0xfee50b7025c36a08, 0x02f236d04753d5b4}, - {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86}, - {0xa6539930bf6bff45, 0x84db8346b786151c}, - {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2}, - {0xd910f7ff28069da4, 0x1b2ba1518094da04}, - {0xaf58416654a6babb, 0x387ac8d1970027b2}, - {0x8da471a9de737e24, 0x5ceaecfed289e5d2}, - {0xe4d5e82392a40515, 0x0fabaf3feaa5334a}, - {0xb8da1662e7b00a17, 0x3d6a751f3b936243}, - { 0x95527a5202df0ccb, - 0x0f37801e0c43ebc8 } + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0} #endif }; @@ -1816,15 +1037,6 @@ template <> struct cache_accessor { 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed, 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9}; - static constexpr const uint32_t pow10_recovery_errors[] = { - 0x50001400, 0x54044100, 0x54014555, 0x55954415, 0x54115555, 0x00000001, - 0x50000000, 0x00104000, 0x54010004, 0x05004001, 0x55555544, 0x41545555, - 0x54040551, 0x15445545, 0x51555514, 0x10000015, 0x00101100, 0x01100015, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04450514, 0x45414110, - 0x55555145, 0x50544050, 0x15040155, 0x11054140, 0x50111514, 0x11451454, - 0x00400541, 0x00000000, 0x55555450, 0x10056551, 0x10054011, 0x55551014, - 0x69514555, 0x05151109, 0x00155555}; - static const int compression_ratio = 27; // Compute base index. @@ -1833,7 +1045,7 @@ template <> struct cache_accessor { int offset = k - kb; // Get base cache. - uint128_wrapper base_cache = pow10_significands[cache_index]; + uint128_fallback base_cache = pow10_significands[cache_index]; if (offset == 0) return base_cache; // Compute the required amount of bit-shift. @@ -1842,9 +1054,8 @@ template <> struct cache_accessor { // Try to recover the real cache. uint64_t pow5 = powers_of_5_64[offset]; - uint128_wrapper recovered_cache = umul128(base_cache.high(), pow5); - uint128_wrapper middle_low = - umul128(base_cache.low() - (kb < 0 ? 1u : 0u), pow5); + uint128_fallback recovered_cache = umul128(base_cache.high(), pow5); + uint128_fallback middle_low = umul128(base_cache.low(), pow5); recovered_cache += middle_low.high(); @@ -1852,227 +1063,156 @@ template <> struct cache_accessor { uint64_t middle_to_low = recovered_cache.low() << (64 - alpha); recovered_cache = - uint128_wrapper{(recovered_cache.low() >> alpha) | high_to_middle, - ((middle_low.low() >> alpha) | middle_to_low)}; - - if (kb < 0) recovered_cache += 1; - - // Get error. - int error_idx = (k - float_info::min_k) / 16; - uint32_t error = (pow10_recovery_errors[error_idx] >> - ((k - float_info::min_k) % 16) * 2) & - 0x3; - - // Add the error back. - FMT_ASSERT(recovered_cache.low() + error >= recovered_cache.low(), ""); - return {recovered_cache.high(), recovered_cache.low() + error}; + uint128_fallback{(recovered_cache.low() >> alpha) | high_to_middle, + ((middle_low.low() >> alpha) | middle_to_low)}; + FMT_ASSERT(recovered_cache.low() + 1 != 0, ""); + return {recovered_cache.high(), recovered_cache.low() + 1}; #endif } - static carrier_uint compute_mul(carrier_uint u, - const cache_entry_type& cache) FMT_NOEXCEPT { - return umul192_upper64(u, cache); + struct compute_mul_result { + carrier_uint result; + bool is_integer; + }; + struct compute_mul_parity_result { + bool parity; + bool is_integer; + }; + + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { + auto r = umul192_upper128(u, cache); + return {r.high(), r.low() == 0}; } - static uint32_t compute_delta(cache_entry_type const& cache, - int beta_minus_1) FMT_NOEXCEPT { - return static_cast(cache.high() >> (64 - 1 - beta_minus_1)); + static auto compute_delta(cache_entry_type const& cache, int beta) noexcept + -> uint32_t { + return static_cast(cache.high() >> (64 - 1 - beta)); } - static bool compute_mul_parity(carrier_uint two_f, + static auto compute_mul_parity(carrier_uint two_f, const cache_entry_type& cache, - int beta_minus_1) FMT_NOEXCEPT { - FMT_ASSERT(beta_minus_1 >= 1, ""); - FMT_ASSERT(beta_minus_1 < 64, ""); + int beta) noexcept + -> compute_mul_parity_result { + FMT_ASSERT(beta >= 1, ""); + FMT_ASSERT(beta < 64, ""); - return ((umul192_middle64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0; + auto r = umul192_lower128(two_f, cache); + return {((r.high() >> (64 - beta)) & 1) != 0, + ((r.high() << beta) | (r.low() >> (64 - beta))) == 0}; } - static carrier_uint compute_left_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() - - (cache.high() >> (float_info::significand_bits + 2))) >> - (64 - float_info::significand_bits - 1 - beta_minus_1); + (cache.high() >> (num_significand_bits() + 2))) >> + (64 - num_significand_bits() - 1 - beta); } - static carrier_uint compute_right_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() + - (cache.high() >> (float_info::significand_bits + 1))) >> - (64 - float_info::significand_bits - 1 - beta_minus_1); + (cache.high() >> (num_significand_bits() + 1))) >> + (64 - num_significand_bits() - 1 - beta); } - static carrier_uint compute_round_up_for_shorter_interval_case( - const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT { - return ((cache.high() >> - (64 - float_info::significand_bits - 2 - beta_minus_1)) + + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return ((cache.high() >> (64 - num_significand_bits() - 2 - beta)) + 1) / 2; } }; -// Various integer checks -template -bool is_left_endpoint_integer_shorter_interval(int exponent) FMT_NOEXCEPT { - return exponent >= - float_info< - T>::case_shorter_interval_left_endpoint_lower_threshold && - exponent <= - float_info::case_shorter_interval_left_endpoint_upper_threshold; -} -template -bool is_endpoint_integer(typename float_info::carrier_uint two_f, - int exponent, int minus_k) FMT_NOEXCEPT { - if (exponent < float_info::case_fc_pm_half_lower_threshold) return false; - // For k >= 0. - if (exponent <= float_info::case_fc_pm_half_upper_threshold) return true; - // For k < 0. - if (exponent > float_info::divisibility_check_by_5_threshold) return false; - return divisible_by_power_of_5(two_f, minus_k); +FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback { + return cache_accessor::get_cached_power(k); } -template -bool is_center_integer(typename float_info::carrier_uint two_f, int exponent, - int minus_k) FMT_NOEXCEPT { - // Exponent for 5 is negative. - if (exponent > float_info::divisibility_check_by_5_threshold) return false; - if (exponent > float_info::case_fc_upper_threshold) - return divisible_by_power_of_5(two_f, minus_k); - // Both exponents are nonnegative. - if (exponent >= float_info::case_fc_lower_threshold) return true; - // Exponent for 2 is negative. - return divisible_by_power_of_2(two_f, minus_k - exponent + 1); +// Various integer checks +template +auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool { + const int case_shorter_interval_left_endpoint_lower_threshold = 2; + const int case_shorter_interval_left_endpoint_upper_threshold = 3; + return exponent >= case_shorter_interval_left_endpoint_lower_threshold && + exponent <= case_shorter_interval_left_endpoint_upper_threshold; } // Remove trailing zeros from n and return the number of zeros removed (float) -FMT_INLINE int remove_trailing_zeros(uint32_t& n) FMT_NOEXCEPT { -#ifdef FMT_BUILTIN_CTZ - int t = FMT_BUILTIN_CTZ(n); -#else - int t = ctz(n); -#endif - if (t > float_info::max_trailing_zeros) - t = float_info::max_trailing_zeros; - - const uint32_t mod_inv1 = 0xcccccccd; - const uint32_t max_quotient1 = 0x33333333; - const uint32_t mod_inv2 = 0xc28f5c29; - const uint32_t max_quotient2 = 0x0a3d70a3; - - int s = 0; - for (; s < t - 1; s += 2) { - if (n * mod_inv2 > max_quotient2) break; - n *= mod_inv2; +FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept { + FMT_ASSERT(n != 0, ""); + // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. + constexpr uint32_t mod_inv_5 = 0xcccccccd; + constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5 + + while (true) { + auto q = rotr(n * mod_inv_25, 2); + if (q > max_value() / 100) break; + n = q; + s += 2; + } + auto q = rotr(n * mod_inv_5, 1); + if (q <= max_value() / 10) { + n = q; + s |= 1; } - if (s < t && n * mod_inv1 <= max_quotient1) { - n *= mod_inv1; - ++s; - } - n >>= s; return s; } // Removes trailing zeros and returns the number of zeros removed (double) -FMT_INLINE int remove_trailing_zeros(uint64_t& n) FMT_NOEXCEPT { -#ifdef FMT_BUILTIN_CTZLL - int t = FMT_BUILTIN_CTZLL(n); -#else - int t = ctzll(n); -#endif - if (t > float_info::max_trailing_zeros) - t = float_info::max_trailing_zeros; - // Divide by 10^8 and reduce to 32-bits - // Since ret_value.significand <= (2^64 - 1) / 1000 < 10^17, - // both of the quotient and the r should fit in 32-bits - - const uint32_t mod_inv1 = 0xcccccccd; - const uint32_t max_quotient1 = 0x33333333; - const uint64_t mod_inv8 = 0xc767074b22e90e21; - const uint64_t max_quotient8 = 0x00002af31dc46118; - - // If the number is divisible by 1'0000'0000, work with the quotient - if (t >= 8) { - auto quotient_candidate = n * mod_inv8; - - if (quotient_candidate <= max_quotient8) { - auto quotient = static_cast(quotient_candidate >> 8); - - int s = 8; - for (; s < t; ++s) { - if (quotient * mod_inv1 > max_quotient1) break; - quotient *= mod_inv1; - } - quotient >>= (s - 8); - n = quotient; - return s; - } - } - - // Otherwise, work with the remainder - auto quotient = static_cast(n / 100000000); - auto remainder = static_cast(n - 100000000 * quotient); - - if (t == 0 || remainder * mod_inv1 > max_quotient1) { - return 0; - } - remainder *= mod_inv1; - - if (t == 1 || remainder * mod_inv1 > max_quotient1) { - n = (remainder >> 1) + quotient * 10000000ull; - return 1; - } - remainder *= mod_inv1; +FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { + FMT_ASSERT(n != 0, ""); - if (t == 2 || remainder * mod_inv1 > max_quotient1) { - n = (remainder >> 2) + quotient * 1000000ull; - return 2; - } - remainder *= mod_inv1; + // This magic number is ceil(2^90 / 10^8). + constexpr uint64_t magic_number = 12379400392853802749ull; + auto nm = umul128(n, magic_number); - if (t == 3 || remainder * mod_inv1 > max_quotient1) { - n = (remainder >> 3) + quotient * 100000ull; - return 3; + // Is n is divisible by 10^8? + if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) { + // If yes, work with the quotient... + auto n32 = static_cast(nm.high() >> (90 - 64)); + // ... and use the 32 bit variant of the function + int s = remove_trailing_zeros(n32, 8); + n = n32; + return s; } - remainder *= mod_inv1; - if (t == 4 || remainder * mod_inv1 > max_quotient1) { - n = (remainder >> 4) + quotient * 10000ull; - return 4; - } - remainder *= mod_inv1; + // If n is not divisible by 10^8, work with n itself. + constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd; + constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // mod_inv_5 * mod_inv_5 - if (t == 5 || remainder * mod_inv1 > max_quotient1) { - n = (remainder >> 5) + quotient * 1000ull; - return 5; + int s = 0; + while (true) { + auto q = rotr(n * mod_inv_25, 2); + if (q > max_value() / 100) break; + n = q; + s += 2; } - remainder *= mod_inv1; - - if (t == 6 || remainder * mod_inv1 > max_quotient1) { - n = (remainder >> 6) + quotient * 100ull; - return 6; + auto q = rotr(n * mod_inv_5, 1); + if (q <= max_value() / 10) { + n = q; + s |= 1; } - remainder *= mod_inv1; - n = (remainder >> 7) + quotient * 10ull; - return 7; + return s; } // The main algorithm for shorter interval case -template -FMT_INLINE decimal_fp shorter_interval_case(int exponent) FMT_NOEXCEPT { +template +FMT_INLINE decimal_fp shorter_interval_case(int exponent) noexcept { decimal_fp ret_value; // Compute k and beta const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); - const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k); + const int beta = exponent + floor_log2_pow10(-minus_k); // Compute xi and zi using cache_entry_type = typename cache_accessor::cache_entry_type; const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); auto xi = cache_accessor::compute_left_endpoint_for_shorter_interval_case( - cache, beta_minus_1); + cache, beta); auto zi = cache_accessor::compute_right_endpoint_for_shorter_interval_case( - cache, beta_minus_1); + cache, beta); // If the left endpoint is not an integer, increase it if (!is_left_endpoint_integer_shorter_interval(exponent)) ++xi; @@ -2089,8 +1229,8 @@ FMT_INLINE decimal_fp shorter_interval_case(int exponent) FMT_NOEXCEPT { // Otherwise, compute the round-up of y ret_value.significand = - cache_accessor::compute_round_up_for_shorter_interval_case( - cache, beta_minus_1); + cache_accessor::compute_round_up_for_shorter_interval_case(cache, + beta); ret_value.exponent = minus_k; // When tie occurs, choose one of them according to the rule @@ -2105,7 +1245,7 @@ FMT_INLINE decimal_fp shorter_interval_case(int exponent) FMT_NOEXCEPT { return ret_value; } -template decimal_fp to_decimal(T x) FMT_NOEXCEPT { +template auto to_decimal(T x) noexcept -> decimal_fp { // Step 1: integer promotion & Schubfach multiplier calculation. using carrier_uint = typename float_info::carrier_uint; @@ -2114,23 +1254,25 @@ template decimal_fp to_decimal(T x) FMT_NOEXCEPT { // Extract significand bits and exponent bits. const carrier_uint significand_mask = - (static_cast(1) << float_info::significand_bits) - 1; + (static_cast(1) << num_significand_bits()) - 1; carrier_uint significand = (br & significand_mask); - int exponent = static_cast((br & exponent_mask()) >> - float_info::significand_bits); + int exponent = + static_cast((br & exponent_mask()) >> num_significand_bits()); if (exponent != 0) { // Check if normal. - exponent += float_info::exponent_bias - float_info::significand_bits; + exponent -= exponent_bias() + num_significand_bits(); // Shorter interval case; proceed like Schubfach. + // In fact, when exponent == 1 and significand == 0, the interval is + // regular. However, it can be shown that the end-results are anyway same. if (significand == 0) return shorter_interval_case(exponent); - significand |= - (static_cast(1) << float_info::significand_bits); + significand |= (static_cast(1) << num_significand_bits()); } else { // Subnormal case; the interval is always regular. if (significand == 0) return {0, 0}; - exponent = float_info::min_exponent - float_info::significand_bits; + exponent = + std::numeric_limits::min_exponent - num_significand_bits() - 1; } const bool include_left_endpoint = (significand % 2 == 0); @@ -2139,419 +1281,118 @@ template decimal_fp to_decimal(T x) FMT_NOEXCEPT { // Compute k and beta. const int minus_k = floor_log10_pow2(exponent) - float_info::kappa; const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); - const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k); + const int beta = exponent + floor_log2_pow10(-minus_k); - // Compute zi and deltai + // Compute zi and deltai. // 10^kappa <= deltai < 10^(kappa + 1) - const uint32_t deltai = cache_accessor::compute_delta(cache, beta_minus_1); + const uint32_t deltai = cache_accessor::compute_delta(cache, beta); const carrier_uint two_fc = significand << 1; - const carrier_uint two_fr = two_fc | 1; - const carrier_uint zi = - cache_accessor::compute_mul(two_fr << beta_minus_1, cache); - // Step 2: Try larger divisor; remove trailing zeros if necessary + // For the case of binary32, the result of integer check is not correct for + // 29711844 * 2^-82 + // = 6.1442653300000000008655037797566933477355632930994033813476... * 10^-18 + // and 29711844 * 2^-81 + // = 1.2288530660000000001731007559513386695471126586198806762695... * 10^-17, + // and they are the unique counterexamples. However, since 29711844 is even, + // this does not cause any problem for the endpoints calculations; it can only + // cause a problem when we need to perform integer check for the center. + // Fortunately, with these inputs, that branch is never executed, so we are + // fine. + const typename cache_accessor::compute_mul_result z_mul = + cache_accessor::compute_mul((two_fc | 1) << beta, cache); + + // Step 2: Try larger divisor; remove trailing zeros if necessary. // Using an upper bound on zi, we might be able to optimize the division - // better than the compiler; we are computing zi / big_divisor here + // better than the compiler; we are computing zi / big_divisor here. decimal_fp ret_value; - ret_value.significand = divide_by_10_to_kappa_plus_1(zi); - uint32_t r = static_cast(zi - float_info::big_divisor * - ret_value.significand); + ret_value.significand = divide_by_10_to_kappa_plus_1(z_mul.result); + uint32_t r = static_cast(z_mul.result - float_info::big_divisor * + ret_value.significand); - if (r > deltai) { - goto small_divisor_case_label; - } else if (r < deltai) { - // Exclude the right endpoint if necessary - if (r == 0 && !include_right_endpoint && - is_endpoint_integer(two_fr, exponent, minus_k)) { + if (r < deltai) { + // Exclude the right endpoint if necessary. + if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) { --ret_value.significand; r = float_info::big_divisor; goto small_divisor_case_label; } + } else if (r > deltai) { + goto small_divisor_case_label; } else { - // r == deltai; compare fractional parts - // Check conditions in the order different from the paper - // to take advantage of short-circuiting - const carrier_uint two_fl = two_fc - 1; - if ((!include_left_endpoint || - !is_endpoint_integer(two_fl, exponent, minus_k)) && - !cache_accessor::compute_mul_parity(two_fl, cache, beta_minus_1)) { + // r == deltai; compare fractional parts. + const typename cache_accessor::compute_mul_parity_result x_mul = + cache_accessor::compute_mul_parity(two_fc - 1, cache, beta); + + if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint))) goto small_divisor_case_label; - } } ret_value.exponent = minus_k + float_info::kappa + 1; - // We may need to remove trailing zeros + // We may need to remove trailing zeros. ret_value.exponent += remove_trailing_zeros(ret_value.significand); return ret_value; - // Step 3: Find the significand with the smaller divisor + // Step 3: Find the significand with the smaller divisor. small_divisor_case_label: ret_value.significand *= 10; ret_value.exponent = minus_k + float_info::kappa; - const uint32_t mask = (1u << float_info::kappa) - 1; - auto dist = r - (deltai / 2) + (float_info::small_divisor / 2); - - // Is dist divisible by 2^kappa? - if ((dist & mask) == 0) { - const bool approx_y_parity = - ((dist ^ (float_info::small_divisor / 2)) & 1) != 0; - dist >>= float_info::kappa; - - // Is dist divisible by 5^kappa? - if (check_divisibility_and_divide_by_pow5::kappa>(dist)) { - ret_value.significand += dist; - - // Check z^(f) >= epsilon^(f) - // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, - // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f) - // Since there are only 2 possibilities, we only need to care about the - // parity. Also, zi and r should have the same parity since the divisor - // is an even number - if (cache_accessor::compute_mul_parity(two_fc, cache, beta_minus_1) != - approx_y_parity) { - --ret_value.significand; - } else { - // If z^(f) >= epsilon^(f), we might have a tie - // when z^(f) == epsilon^(f), or equivalently, when y is an integer - if (is_center_integer(two_fc, exponent, minus_k)) { - ret_value.significand = ret_value.significand % 2 == 0 - ? ret_value.significand - : ret_value.significand - 1; - } - } - } - // Is dist not divisible by 5^kappa? - else { - ret_value.significand += dist; - } - } - // Is dist not divisible by 2^kappa? - else { - // Since we know dist is small, we might be able to optimize the division - // better than the compiler; we are computing dist / small_divisor here - ret_value.significand += - small_division_by_pow10::kappa>(dist); - } + uint32_t dist = r - (deltai / 2) + (float_info::small_divisor / 2); + const bool approx_y_parity = + ((dist ^ (float_info::small_divisor / 2)) & 1) != 0; + + // Is dist divisible by 10^kappa? + const bool divisible_by_small_divisor = + check_divisibility_and_divide_by_pow10::kappa>(dist); + + // Add dist / 10^kappa to the significand. + ret_value.significand += dist; + + if (!divisible_by_small_divisor) return ret_value; + + // Check z^(f) >= epsilon^(f). + // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, + // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f). + // Since there are only 2 possibilities, we only need to care about the + // parity. Also, zi and r should have the same parity since the divisor + // is an even number. + const auto y_mul = cache_accessor::compute_mul_parity(two_fc, cache, beta); + + // If z^(f) >= epsilon^(f), we might have a tie when z^(f) == epsilon^(f), + // or equivalently, when y is an integer. + if (y_mul.parity != approx_y_parity) + --ret_value.significand; + else if (y_mul.is_integer & (ret_value.significand % 2 != 0)) + --ret_value.significand; return ret_value; } } // namespace dragonbox - -// Formats value using a variation of the Fixed-Precision Positive -// Floating-Point Printout ((FPP)^2) algorithm by Steele & White: -// https://fmt.dev/papers/p372-steele.pdf. -template -FMT_CONSTEXPR20 void fallback_format(Double d, int num_digits, bool binary32, - buffer& buf, int& exp10) { - bigint numerator; // 2 * R in (FPP)^2. - bigint denominator; // 2 * S in (FPP)^2. - // lower and upper are differences between value and corresponding boundaries. - bigint lower; // (M^- in (FPP)^2). - bigint upper_store; // upper's value if different from lower. - bigint* upper = nullptr; // (M^+ in (FPP)^2). - fp value; - // Shift numerator and denominator by an extra bit or two (if lower boundary - // is closer) to make lower and upper integers. This eliminates multiplication - // by 2 during later computations. - const bool is_predecessor_closer = - binary32 ? value.assign(static_cast(d)) : value.assign(d); - int shift = is_predecessor_closer ? 2 : 1; - uint64_t significand = value.f << shift; - if (value.e >= 0) { - numerator.assign(significand); - numerator <<= value.e; - lower.assign(1); - lower <<= value.e; - if (shift != 1) { - upper_store.assign(1); - upper_store <<= value.e + 1; - upper = &upper_store; - } - denominator.assign_pow10(exp10); - denominator <<= shift; - } else if (exp10 < 0) { - numerator.assign_pow10(-exp10); - lower.assign(numerator); - if (shift != 1) { - upper_store.assign(numerator); - upper_store <<= 1; - upper = &upper_store; - } - numerator *= significand; - denominator.assign(1); - denominator <<= shift - value.e; - } else { - numerator.assign(significand); - denominator.assign_pow10(exp10); - denominator <<= shift - value.e; - lower.assign(1); - if (shift != 1) { - upper_store.assign(1ULL << 1); - upper = &upper_store; - } - } - // Invariant: value == (numerator / denominator) * pow(10, exp10). - if (num_digits < 0) { - // Generate the shortest representation. - if (!upper) upper = &lower; - bool even = (value.f & 1) == 0; - num_digits = 0; - char* data = buf.data(); - for (;;) { - int digit = numerator.divmod_assign(denominator); - bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. - // numerator + upper >[=] pow10: - bool high = add_compare(numerator, *upper, denominator) + even > 0; - data[num_digits++] = static_cast('0' + digit); - if (low || high) { - if (!low) { - ++data[num_digits - 1]; - } else if (high) { - int result = add_compare(numerator, numerator, denominator); - // Round half to even. - if (result > 0 || (result == 0 && (digit % 2) != 0)) - ++data[num_digits - 1]; - } - buf.try_resize(to_unsigned(num_digits)); - exp10 -= num_digits - 1; - return; - } - numerator *= 10; - lower *= 10; - if (upper != &lower) *upper *= 10; - } - } - // Generate the given number of digits. - exp10 -= num_digits - 1; - if (num_digits == 0) { - buf.try_resize(1); - denominator *= 10; - buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; - return; - } - buf.try_resize(to_unsigned(num_digits)); - for (int i = 0; i < num_digits - 1; ++i) { - int digit = numerator.divmod_assign(denominator); - buf[i] = static_cast('0' + digit); - numerator *= 10; - } - int digit = numerator.divmod_assign(denominator); - auto result = add_compare(numerator, numerator, denominator); - if (result > 0 || (result == 0 && (digit % 2) != 0)) { - if (digit == 9) { - const auto overflow = '0' + 10; - buf[num_digits - 1] = overflow; - // Propagate the carry. - for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { - buf[i] = '0'; - ++buf[i - 1]; - } - if (buf[0] == overflow) { - buf[0] = '1'; - ++exp10; - } - return; - } - ++digit; - } - buf[num_digits - 1] = static_cast('0' + digit); -} - -template -FMT_HEADER_ONLY_CONSTEXPR20 int format_float(T value, int precision, - float_specs specs, - buffer& buf) { - static_assert(!std::is_same::value, ""); - FMT_ASSERT(value >= 0, "value is negative"); - - const bool fixed = specs.format == float_format::fixed; - if (value <= 0) { // <= instead of == to silence a warning. - if (precision <= 0 || !fixed) { - buf.push_back('0'); - return 0; - } - buf.try_resize(to_unsigned(precision)); - fill_n(buf.data(), precision, '0'); - return -precision; - } - - if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf); - - if (!is_constant_evaluated() && precision < 0) { - // Use Dragonbox for the shortest format. - if (specs.binary32) { - auto dec = dragonbox::to_decimal(static_cast(value)); - write(buffer_appender(buf), dec.significand); - return dec.exponent; - } - auto dec = dragonbox::to_decimal(static_cast(value)); - write(buffer_appender(buf), dec.significand); - return dec.exponent; - } - - // Use Grisu + Dragon4 for the given precision: - // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. - int exp = 0; - const int min_exp = -60; // alpha in Grisu. - int cached_exp10 = 0; // K in Grisu. - fp normalized = normalize(fp(value)); - const auto cached_pow = get_cached_power( - min_exp - (normalized.e + fp::significand_size), cached_exp10); - normalized = normalized * cached_pow; - // Limit precision to the maximum possible number of significant digits in an - // IEEE754 double because we don't need to generate zeros. - const int max_double_digits = 767; - if (precision > max_double_digits) precision = max_double_digits; - fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; - if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error || - is_constant_evaluated()) { - exp += handler.size - cached_exp10 - 1; - fallback_format(value, handler.precision, specs.binary32, buf, exp); - } else { - exp += handler.exp10; - buf.try_resize(to_unsigned(handler.size)); - } - if (!fixed && !specs.showpoint) { - // Remove trailing zeros. - auto num_digits = buf.size(); - while (num_digits > 0 && buf[num_digits - 1] == '0') { - --num_digits; - ++exp; - } - buf.try_resize(num_digits); - } - return exp; -} // namespace detail - -template -int snprintf_float(T value, int precision, float_specs specs, - buffer& buf) { - // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. - FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer"); - static_assert(!std::is_same::value, ""); - - // Subtract 1 to account for the difference in precision since we use %e for - // both general and exponent format. - if (specs.format == float_format::general || - specs.format == float_format::exp) - precision = (precision >= 0 ? precision : 6) - 1; - - // Build the format string. - enum { max_format_size = 7 }; // The longest format is "%#.*Le". - char format[max_format_size]; - char* format_ptr = format; - *format_ptr++ = '%'; - if (specs.showpoint && specs.format == float_format::hex) *format_ptr++ = '#'; - if (precision >= 0) { - *format_ptr++ = '.'; - *format_ptr++ = '*'; - } - if (std::is_same()) *format_ptr++ = 'L'; - *format_ptr++ = specs.format != float_format::hex - ? (specs.format == float_format::fixed ? 'f' : 'e') - : (specs.upper ? 'A' : 'a'); - *format_ptr = '\0'; - - // Format using snprintf. - auto offset = buf.size(); - for (;;) { - auto begin = buf.data() + offset; - auto capacity = buf.capacity() - offset; -#ifdef FMT_FUZZ - if (precision > 100000) - throw std::runtime_error( - "fuzz mode - avoid large allocation inside snprintf"); -#endif - // Suppress the warning about a nonliteral format string. - // Cannot use auto because of a bug in MinGW (#1532). - int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; - int result = precision >= 0 - ? snprintf_ptr(begin, capacity, format, precision, value) - : snprintf_ptr(begin, capacity, format, value); - if (result < 0) { - // The buffer will grow exponentially. - buf.try_reserve(buf.capacity() + 1); - continue; - } - auto size = to_unsigned(result); - // Size equal to capacity means that the last character was truncated. - if (size >= capacity) { - buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'. - continue; - } - auto is_digit = [](char c) { return c >= '0' && c <= '9'; }; - if (specs.format == float_format::fixed) { - if (precision == 0) { - buf.try_resize(size); - return 0; - } - // Find and remove the decimal point. - auto end = begin + size, p = end; - do { - --p; - } while (is_digit(*p)); - int fraction_size = static_cast(end - p - 1); - std::memmove(p, p + 1, to_unsigned(fraction_size)); - buf.try_resize(size - 1); - return -fraction_size; - } - if (specs.format == float_format::hex) { - buf.try_resize(size + offset); - return 0; - } - // Find and parse the exponent. - auto end = begin + size, exp_pos = end; - do { - --exp_pos; - } while (*exp_pos != 'e'); - char sign = exp_pos[1]; - FMT_ASSERT(sign == '+' || sign == '-', ""); - int exp = 0; - auto p = exp_pos + 2; // Skip 'e' and sign. - do { - FMT_ASSERT(is_digit(*p), ""); - exp = exp * 10 + (*p++ - '0'); - } while (p != end); - if (sign == '-') exp = -exp; - int fraction_size = 0; - if (exp_pos != begin + 1) { - // Remove trailing zeros. - auto fraction_end = exp_pos - 1; - while (*fraction_end == '0') --fraction_end; - // Move the fractional part left to get rid of the decimal point. - fraction_size = static_cast(fraction_end - begin - 1); - std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size)); - } - buf.try_resize(to_unsigned(fraction_size) + offset + 1); - return exp - fraction_size; - } -} } // namespace detail template <> struct formatter { - FMT_CONSTEXPR format_parse_context::iterator parse( - format_parse_context& ctx) { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) + -> format_parse_context::iterator { return ctx.begin(); } - format_context::iterator format(const detail::bigint& n, - format_context& ctx) { + auto format(const detail::bigint& n, format_context& ctx) const + -> format_context::iterator { auto out = ctx.out(); bool first = true; for (auto i = n.bigits_.size(); i > 0; --i) { auto value = n.bigits_[i - 1u]; if (first) { - out = format_to(out, FMT_STRING("{:x}"), value); + out = fmt::format_to(out, FMT_STRING("{:x}"), value); first = false; continue; } - out = format_to(out, FMT_STRING("{:08x}"), value); + out = fmt::format_to(out, FMT_STRING("{:08x}"), value); } if (n.exp_ > 0) - out = format_to(out, FMT_STRING("p{}"), - n.exp_ * detail::bigint::bigit_bits); + out = fmt::format_to(out, FMT_STRING("p{}"), + n.exp_ * detail::bigint::bigit_bits); return out; } }; @@ -2572,10 +1413,10 @@ FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { } FMT_FUNC void format_system_error(detail::buffer& out, int error_code, - const char* message) FMT_NOEXCEPT { + const char* message) noexcept { FMT_TRY { auto ec = std::error_code(error_code, std::generic_category()); - write(std::back_inserter(out), std::system_error(ec, message).what()); + detail::write(appender(out), std::system_error(ec, message).what()); return; } FMT_CATCH(...) {} @@ -2583,11 +1424,11 @@ FMT_FUNC void format_system_error(detail::buffer& out, int error_code, } FMT_FUNC void report_system_error(int error_code, - const char* message) FMT_NOEXCEPT { + const char* message) noexcept { report_error(format_system_error, error_code, message); } -FMT_FUNC std::string vformat(string_view fmt, format_args args) { +FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { // Don't optimize the "{}" case to keep the binary size small and because it // can be better optimized in fmt::format anyway. auto buffer = memory_buffer(); @@ -2595,55 +1436,462 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) { return to_string(buffer); } -#ifdef _WIN32 namespace detail { + +template struct span { + T* data; + size_t size; +}; + +#ifdef _WIN32 +inline void flockfile(FILE* f) { _lock_file(f); } +inline void funlockfile(FILE* f) { _unlock_file(f); } +inline int getc_unlocked(FILE* f) { return _fgetc_nolock(f); } +#endif + +// A FILE wrapper. F is FILE defined as a template parameter to make system API +// detection work. +template class file_base { + public: + F* file_; + + public: + file_base(F* file) : file_(file) {} + operator F*() const { return file_; } + + // Reads a code unit from the stream. + auto get() -> int { + int result = getc_unlocked(file_); + if (result == EOF && ferror(file_) != 0) + FMT_THROW(system_error(errno, FMT_STRING("getc failed"))); + return result; + } + + // Puts the code unit back into the stream buffer. + void unget(char c) { + if (ungetc(c, file_) == EOF) + FMT_THROW(system_error(errno, FMT_STRING("ungetc failed"))); + } + + void flush() { fflush(this->file_); } +}; + +// A FILE wrapper for glibc. +template class glibc_file : public file_base { + private: + enum { + line_buffered = 0x200, // _IO_LINE_BUF + unbuffered = 2 // _IO_UNBUFFERED + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; + } + + void init_buffer() { + if (this->file_->_IO_write_ptr) return; + // Force buffer initialization by placing and removing a char in a buffer. + putc_unlocked(0, this->file_); + --this->file_->_IO_write_ptr; + } + + // Returns the file's read buffer. + auto get_read_buffer() const -> span { + auto ptr = this->file_->_IO_read_ptr; + return {ptr, to_unsigned(this->file_->_IO_read_end - ptr)}; + } + + // Returns the file's write buffer. + auto get_write_buffer() const -> span { + auto ptr = this->file_->_IO_write_ptr; + return {ptr, to_unsigned(this->file_->_IO_buf_end - ptr)}; + } + + void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; } + + bool needs_flush() const { + if ((this->file_->_flags & line_buffered) == 0) return false; + char* end = this->file_->_IO_write_end; + return memchr(end, '\n', to_unsigned(this->file_->_IO_write_ptr - end)); + } + + void flush() { fflush_unlocked(this->file_); } +}; + +// A FILE wrapper for Apple's libc. +template class apple_file : public file_base { + private: + enum { + line_buffered = 1, // __SNBF + unbuffered = 2 // __SLBF + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; + } + + void init_buffer() { + if (this->file_->_p) return; + // Force buffer initialization by placing and removing a char in a buffer. + putc_unlocked(0, this->file_); + --this->file_->_p; + ++this->file_->_w; + } + + auto get_read_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_r)}; + } + + auto get_write_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_bf._base + this->file_->_bf._size - + this->file_->_p)}; + } + + void advance_write_buffer(size_t size) { + this->file_->_p += size; + this->file_->_w -= size; + } + + bool needs_flush() const { + if ((this->file_->_flags & line_buffered) == 0) return false; + return memchr(this->file_->_p + this->file_->_w, '\n', + to_unsigned(-this->file_->_w)); + } +}; + +// A fallback FILE wrapper. +template class fallback_file : public file_base { + private: + char next_; // The next unconsumed character in the buffer. + bool has_next_ = false; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { return false; } + auto needs_flush() const -> bool { return false; } + void init_buffer() {} + + auto get_read_buffer() const -> span { + return {&next_, has_next_ ? 1u : 0u}; + } + + auto get_write_buffer() const -> span { return {nullptr, 0}; } + + void advance_write_buffer(size_t) {} + + auto get() -> int { + has_next_ = false; + return file_base::get(); + } + + void unget(char c) { + file_base::unget(c); + next_ = c; + has_next_ = true; + } +}; + +template +auto get_file(F* f, int) -> apple_file { + return f; +} +template +inline auto get_file(F* f, int) -> glibc_file { + return f; +} +inline auto get_file(FILE* f, ...) -> fallback_file { return f; } + +using file_ref = decltype(get_file(static_cast(nullptr), 0)); + +class file_print_buffer : public buffer { + private: + file_ref file_; + + static void grow(buffer& base, size_t) { + auto& self = static_cast(base); + self.file_.advance_write_buffer(self.size()); + if (self.file_.get_write_buffer().size == 0) self.file_.flush(); + auto buf = self.file_.get_write_buffer(); + FMT_ASSERT(buf.size > 0, ""); + self.set(buf.data, buf.size); + self.clear(); + } + + public: + explicit file_print_buffer(FILE* f) : buffer(grow, size_t()), file_(f) { + flockfile(f); + file_.init_buffer(); + auto buf = file_.get_write_buffer(); + set(buf.data, buf.size); + } + ~file_print_buffer() { + file_.advance_write_buffer(size()); + bool flush = file_.needs_flush(); + funlockfile(file_); + if (flush) fflush(file_); + } +}; + +#if !defined(_WIN32) || defined(FMT_USE_WRITE_CONSOLE) +FMT_FUNC auto write_console(int, string_view) -> bool { return false; } +#else using dword = conditional_t; extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // void*, const void*, dword, dword*, void*); -} // namespace detail + +FMT_FUNC bool write_console(int fd, string_view text) { + auto u16 = utf8_to_utf16(text); + return WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), + static_cast(u16.size()), nullptr, nullptr) != 0; +} #endif -namespace detail { -FMT_FUNC void print(std::FILE* f, string_view text) { #ifdef _WIN32 - auto fd = _fileno(f); +// Print assuming legacy (non-Unicode) encoding. +FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args, + bool newline) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + if (newline) buffer.push_back('\n'); + fwrite_fully(buffer.data(), buffer.size(), f); +} +#endif + +FMT_FUNC void print(std::FILE* f, string_view text) { +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) + int fd = _fileno(f); if (_isatty(fd)) { - detail::utf8_to_utf16 u16(string_view(text.data(), text.size())); - auto written = detail::dword(); - if (detail::WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), - u16.c_str(), static_cast(u16.size()), - &written, nullptr)) { - return; - } - // Fallback to fwrite on failure. It can happen if the output has been - // redirected to NUL. + std::fflush(f); + if (write_console(fd, text)) return; } #endif - detail::fwrite_fully(text.data(), 1, text.size(), f); + fwrite_fully(text.data(), text.size(), f); } } // namespace detail -FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { - memory_buffer buffer; - detail::vformat_to(buffer, format_str, args); +FMT_FUNC void vprint_buffered(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); detail::print(f, {buffer.data(), buffer.size()}); } -#ifdef _WIN32 -// Print assuming legacy (non-Unicode) encoding. -FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str, - format_args args) { - memory_buffer buffer; - detail::vformat_to(buffer, format_str, - basic_format_args>(args)); - fwrite_fully(buffer.data(), 1, buffer.size(), f); +FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { + if (!detail::file_ref(f).is_buffered()) return vprint_buffered(f, fmt, args); + auto&& buffer = detail::file_print_buffer(f); + return detail::vformat_to(buffer, fmt, args); } -#endif -FMT_FUNC void vprint(string_view format_str, format_args args) { - vprint(stdout, format_str, args); +FMT_FUNC void vprintln(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + buffer.push_back('\n'); + detail::print(f, {buffer.data(), buffer.size()}); } +FMT_FUNC void vprint(string_view fmt, format_args args) { + vprint(stdout, fmt, args); +} + +namespace detail { + +struct singleton { + unsigned char upper; + unsigned char lower_count; +}; + +inline auto is_printable(uint16_t x, const singleton* singletons, + size_t singletons_size, + const unsigned char* singleton_lowers, + const unsigned char* normal, size_t normal_size) + -> bool { + auto upper = x >> 8; + auto lower_start = 0; + for (size_t i = 0; i < singletons_size; ++i) { + auto s = singletons[i]; + auto lower_end = lower_start + s.lower_count; + if (upper < s.upper) break; + if (upper == s.upper) { + for (auto j = lower_start; j < lower_end; ++j) { + if (singleton_lowers[j] == (x & 0xff)) return false; + } + } + lower_start = lower_end; + } + + auto xsigned = static_cast(x); + auto current = true; + for (size_t i = 0; i < normal_size; ++i) { + auto v = static_cast(normal[i]); + auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v; + xsigned -= len; + if (xsigned < 0) break; + current = !current; + } + return current; +} + +// This code is generated by support/printable.py. +FMT_FUNC auto is_printable(uint32_t cp) -> bool { + static constexpr singleton singletons0[] = { + {0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8}, + {0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13}, + {0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5}, + {0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22}, + {0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3}, + {0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8}, + {0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9}, + }; + static constexpr unsigned char singletons0_lower[] = { + 0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90, + 0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f, + 0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1, + 0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04, + 0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d, + 0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf, + 0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d, + 0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d, + 0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d, + 0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5, + 0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7, + 0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49, + 0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7, + 0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7, + 0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e, + 0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16, + 0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e, + 0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f, + 0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf, + 0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0, + 0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27, + 0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91, + 0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7, + 0xfe, 0xff, + }; + static constexpr singleton singletons1[] = { + {0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2}, + {0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5}, + {0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5}, + {0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2}, + {0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5}, + {0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2}, + {0xfa, 2}, {0xfb, 1}, + }; + static constexpr unsigned char singletons1_lower[] = { + 0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07, + 0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36, + 0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87, + 0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b, + 0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9, + 0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66, + 0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27, + 0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc, + 0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7, + 0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6, + 0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c, + 0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66, + 0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0, + 0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93, + }; + static constexpr unsigned char normal0[] = { + 0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04, + 0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0, + 0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01, + 0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03, + 0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03, + 0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a, + 0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15, + 0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f, + 0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80, + 0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07, + 0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06, + 0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04, + 0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac, + 0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c, + 0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11, + 0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c, + 0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b, + 0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6, + 0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03, + 0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80, + 0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06, + 0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c, + 0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17, + 0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80, + 0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80, + 0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d, + }; + static constexpr unsigned char normal1[] = { + 0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f, + 0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e, + 0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04, + 0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09, + 0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16, + 0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f, + 0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36, + 0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33, + 0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08, + 0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e, + 0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41, + 0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03, + 0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22, + 0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04, + 0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45, + 0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03, + 0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81, + 0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75, + 0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1, + 0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a, + 0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11, + 0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09, + 0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89, + 0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6, + 0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09, + 0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50, + 0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05, + 0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83, + 0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05, + 0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80, + 0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80, + 0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07, + 0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e, + 0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07, + 0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06, + }; + auto lower = static_cast(cp); + if (cp < 0x10000) { + return is_printable(lower, singletons0, + sizeof(singletons0) / sizeof(*singletons0), + singletons0_lower, normal0, sizeof(normal0)); + } + if (cp < 0x20000) { + return is_printable(lower, singletons1, + sizeof(singletons1) / sizeof(*singletons1), + singletons1_lower, normal1, sizeof(normal1)); + } + if (0x2a6de <= cp && cp < 0x2a700) return false; + if (0x2b735 <= cp && cp < 0x2b740) return false; + if (0x2b81e <= cp && cp < 0x2b820) return false; + if (0x2cea2 <= cp && cp < 0x2ceb0) return false; + if (0x2ebe1 <= cp && cp < 0x2f800) return false; + if (0x2fa1e <= cp && cp < 0x30000) return false; + if (0x3134b <= cp && cp < 0xe0100) return false; + if (0xe01f0 <= cp && cp < 0x110000) return false; + return cp < 0x110000; +} + +} // namespace detail + FMT_END_NAMESPACE #endif // FMT_FORMAT_INL_H_ diff --git a/src/3rdparty/fmt/format.h b/src/3rdparty/fmt/format.h index dd0006bc..fa8debc1 100644 --- a/src/3rdparty/fmt/format.h +++ b/src/3rdparty/fmt/format.h @@ -1,66 +1,100 @@ /* - Formatting library for C++ - - Copyright (c) 2012 - present, Victor Zverovich - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - --- Optional exception to the license --- - - As an exception, if, as a result of your compiling your source code, portions - of this Software are embedded into a machine-executable object form of such - source code, you may redistribute such embedded portions in such object form - without including the above copyright and permission notices. + Formatting library for C++ + + Copyright (c) 2012 - present, Victor Zverovich + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --- Optional exception to the license --- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into a machine-executable object form of such + source code, you may redistribute such embedded portions in such object form + without including the above copyright and permission notices. */ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#include // std::signbit -#include // uint32_t -#include // std::numeric_limits -#include // std::uninitialized_copy -#include // std::runtime_error -#include // std::system_error -#include // std::swap +#ifndef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define FMT_REMOVE_TRANSITIVE_INCLUDES +#endif -#include "core.h" +#ifndef FMT_IMPORT_STD +# include // std::signbit +# include // uint32_t +# include // std::memcpy +# include // std::initializer_list +# include // std::numeric_limits +# if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI) +// Workaround for pre gcc 5 libstdc++. +# include // std::allocator_traits +# endif +# include // std::runtime_error +# include // std::string +# include // std::system_error +#endif -#if FMT_GCC_VERSION -# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) -#else -# define FMT_GCC_VISIBILITY_HIDDEN +#include "base.h" + +// Checking FMT_CPLUSPLUS for warning suppression in MSVC. +#if FMT_HAS_INCLUDE() && FMT_CPLUSPLUS > 201703L && \ + !defined(FMT_IMPORT_STD) +# include // std::bit_cast +#endif + +// libc++ supports string_view in pre-c++17. +#if FMT_HAS_INCLUDE() && \ + (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) +# ifndef FMT_IMPORT_STD +# include +# endif +# define FMT_USE_STRING_VIEW #endif -#ifdef __INTEL_COMPILER -# define FMT_ICC_VERSION __INTEL_COMPILER -#elif defined(__ICL) -# define FMT_ICC_VERSION __ICL +#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L +# define FMT_INLINE_VARIABLE inline #else -# define FMT_ICC_VERSION 0 +# define FMT_INLINE_VARIABLE #endif -#ifdef __NVCC__ -# define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) +#ifndef FMT_NO_UNIQUE_ADDRESS +# if FMT_CPLUSPLUS >= 202002L +# if FMT_HAS_CPP_ATTRIBUTE(no_unique_address) +# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] +// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485). +# elif (FMT_MSC_VERSION >= 1929) && !FMT_CLANG_VERSION +# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +# endif +# endif +#endif +#ifndef FMT_NO_UNIQUE_ADDRESS +# define FMT_NO_UNIQUE_ADDRESS +#endif + +// Visibility when compiled as a shared library/object. +#if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_SO_VISIBILITY(value) FMT_VISIBILITY(value) #else -# define FMT_CUDA_VERSION 0 +# define FMT_SO_VISIBILITY(value) #endif #ifdef __has_builtin @@ -75,15 +109,9 @@ # define FMT_NOINLINE #endif -#if FMT_MSC_VER -# define FMT_MSC_DEFAULT = default -#else -# define FMT_MSC_DEFAULT -#endif - #ifndef FMT_THROW # if FMT_EXCEPTIONS -# if FMT_MSC_VER || FMT_NVCC +# if FMT_MSC_VERSION || defined(__NVCC__) FMT_BEGIN_NAMESPACE namespace detail { template inline void do_throw(const Exception& x) { @@ -99,32 +127,8 @@ FMT_END_NAMESPACE # define FMT_THROW(x) throw x # endif # else -# define FMT_THROW(x) \ - do { \ - FMT_ASSERT(false, (x).what()); \ - } while (false) -# endif -#endif - -#if FMT_EXCEPTIONS -# define FMT_TRY try -# define FMT_CATCH(x) catch (x) -#else -# define FMT_TRY if (true) -# define FMT_CATCH(x) if (false) -#endif - -#ifndef FMT_DEPRECATED -# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900 -# define FMT_DEPRECATED [[deprecated]] -# else -# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) -# define FMT_DEPRECATED __attribute__((deprecated)) -# elif FMT_MSC_VER -# define FMT_DEPRECATED __declspec(deprecated) -# else -# define FMT_DEPRECATED /* deprecated */ -# endif +# define FMT_THROW(x) \ + ::fmt::detail::assert_fail(__FILE__, __LINE__, (x).what()) # endif #endif @@ -136,17 +140,13 @@ FMT_END_NAMESPACE # endif #endif -// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers. -#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC -# define FMT_DEPRECATED_ALIAS -#else -# define FMT_DEPRECATED_ALIAS FMT_DEPRECATED -#endif - #ifndef FMT_USE_USER_DEFINED_LITERALS // EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs. -# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ - FMT_MSC_VER >= 1900) && \ +// +// GCC before 4.9 requires a space in `operator"" _a` which is invalid in later +// compiler versions. +# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 409 || \ + FMT_MSC_VERSION >= 1900) && \ (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480) # define FMT_USE_USER_DEFINED_LITERALS 1 # else @@ -163,36 +163,42 @@ FMT_END_NAMESPACE #endif // __builtin_clz is broken in clang with Microsoft CodeGen: -// https://github.com/fmtlib/fmt/issues/519 -#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz) || FMT_ICC_VERSION) && \ - !FMT_MSC_VER -# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) -#endif -#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll) || \ - FMT_ICC_VERSION) && \ - !FMT_MSC_VER -# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) -#endif -#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctz) || FMT_ICC_VERSION) -# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) +// https://github.com/fmtlib/fmt/issues/519. +#if !FMT_MSC_VERSION +# if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +# endif +# if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +# endif #endif -#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctzll) || FMT_ICC_VERSION) -# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) + +// __builtin_ctz is broken in Intel Compiler Classic on Windows: +// https://github.com/fmtlib/fmt/issues/2510. +#ifndef __ICL +# if FMT_HAS_BUILTIN(__builtin_ctz) || FMT_GCC_VERSION || FMT_ICC_VERSION || \ + defined(__NVCOMPILER) +# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) +# endif +# if FMT_HAS_BUILTIN(__builtin_ctzll) || FMT_GCC_VERSION || \ + FMT_ICC_VERSION || defined(__NVCOMPILER) +# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) +# endif #endif -#if FMT_MSC_VER +#if FMT_MSC_VERSION # include // _BitScanReverse[64], _BitScanForward[64], _umul128 #endif // Some compilers masquerade as both MSVC and GCC-likes or otherwise support // __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the // MSVC intrinsics if the clz and clzll builtins are not available. -#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(FMT_BUILTIN_CTZLL) +#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) && \ + !defined(FMT_BUILTIN_CTZLL) FMT_BEGIN_NAMESPACE namespace detail { // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. # if !defined(__clang__) -# pragma managed(push, off) # pragma intrinsic(_BitScanForward) # pragma intrinsic(_BitScanReverse) # if defined(_WIN64) @@ -219,7 +225,8 @@ inline auto clzll(uint64_t x) -> int { _BitScanReverse64(&r, x); # else // Scan the high 32 bits. - if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 ^ (r + 32); + if (_BitScanReverse(&r, static_cast(x >> 32))) + return 63 ^ static_cast(r + 32); // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); # endif @@ -254,68 +261,175 @@ inline auto ctzll(uint64_t x) -> int { return static_cast(r); } # define FMT_BUILTIN_CTZLL(n) detail::ctzll(n) -# if !defined(__clang__) -# pragma managed(pop) -# endif } // namespace detail FMT_END_NAMESPACE #endif -#ifdef FMT_HEADER_ONLY -# define FMT_HEADER_ONLY_CONSTEXPR20 FMT_CONSTEXPR20 +FMT_BEGIN_NAMESPACE + +template +struct is_contiguous> + : std::true_type {}; + +namespace detail { + +FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { + ignore_unused(condition); +#ifdef FMT_FUZZ + if (condition) throw std::runtime_error("fuzzing limit reached"); +#endif +} + +#if defined(FMT_USE_STRING_VIEW) +template using std_string_view = std::basic_string_view; #else -# define FMT_HEADER_ONLY_CONSTEXPR20 +template struct std_string_view {}; #endif -FMT_BEGIN_NAMESPACE -namespace detail { -// An equivalent of `*reinterpret_cast(&source)` that doesn't have -// undefined behavior (e.g. due to type aliasing). -// Example: uint64_t d = bit_cast(2.718); -template -FMT_CONSTEXPR20 auto bit_cast(const Source& source) -> Dest { - static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); +// Implementation of std::bit_cast for pre-C++20. +template +FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { #ifdef __cpp_lib_bit_cast - if (is_constant_evaluated()) { - return std::bit_cast(source); - } + if (is_constant_evaluated()) return std::bit_cast(from); #endif - Dest dest; - std::memcpy(&dest, &source, sizeof(dest)); - return dest; + auto to = To(); + // The cast suppresses a bogus -Wclass-memaccess on GCC. + std::memcpy(static_cast(&to), &from, sizeof(to)); + return to; } inline auto is_big_endian() -> bool { - const auto u = 1u; +#ifdef _WIN32 + return false; +#elif defined(__BIG_ENDIAN__) + return true; +#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) + return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; +#else struct bytes { - char data[sizeof(u)]; + char data[sizeof(int)]; }; - return bit_cast(u).data[0] == 0; + return bit_cast(1).data[0] == 0; +#endif } -// A fallback implementation of uintptr_t for systems that lack it. -struct fallback_uintptr { - unsigned char value[sizeof(void*)]; +class uint128_fallback { + private: + uint64_t lo_, hi_; + + public: + constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} + constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} + + constexpr auto high() const noexcept -> uint64_t { return hi_; } + constexpr auto low() const noexcept -> uint64_t { return lo_; } + + template ::value)> + constexpr explicit operator T() const { + return static_cast(lo_); + } + + friend constexpr auto operator==(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ == rhs.hi_ && lhs.lo_ == rhs.lo_; + } + friend constexpr auto operator!=(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return !(lhs == rhs); + } + friend constexpr auto operator>(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_; + } + friend constexpr auto operator|(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_}; + } + friend constexpr auto operator&(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; + } + friend constexpr auto operator~(const uint128_fallback& n) + -> uint128_fallback { + return {~n.hi_, ~n.lo_}; + } + friend auto operator+(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> uint128_fallback { + auto result = uint128_fallback(lhs); + result += rhs; + return result; + } + friend auto operator*(const uint128_fallback& lhs, uint32_t rhs) + -> uint128_fallback { + FMT_ASSERT(lhs.hi_ == 0, ""); + uint64_t hi = (lhs.lo_ >> 32) * rhs; + uint64_t lo = (lhs.lo_ & ~uint32_t()) * rhs; + uint64_t new_lo = (hi << 32) + lo; + return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; + } + friend auto operator-(const uint128_fallback& lhs, uint64_t rhs) + -> uint128_fallback { + return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; + } + FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback { + if (shift == 64) return {0, hi_}; + if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64); + return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)}; + } + FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback { + if (shift == 64) return {lo_, 0}; + if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64); + return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)}; + } + FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& { + return *this = *this >> shift; + } + FMT_CONSTEXPR void operator+=(uint128_fallback n) { + uint64_t new_lo = lo_ + n.lo_; + uint64_t new_hi = hi_ + n.hi_ + (new_lo < lo_ ? 1 : 0); + FMT_ASSERT(new_hi >= hi_, ""); + lo_ = new_lo; + hi_ = new_hi; + } + FMT_CONSTEXPR void operator&=(uint128_fallback n) { + lo_ &= n.lo_; + hi_ &= n.hi_; + } - fallback_uintptr() = default; - explicit fallback_uintptr(const void* p) { - *this = bit_cast(p); - if (is_big_endian()) { - for (size_t i = 0, j = sizeof(void*) - 1; i < j; ++i, --j) - std::swap(value[i], value[j]); + FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& { + if (is_constant_evaluated()) { + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); + return *this; } +#if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) + unsigned long long carry; + lo_ = __builtin_addcll(lo_, n, 0, &carry); + hi_ += carry; +#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__) + unsigned long long result; + auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result); + lo_ = result; + hi_ += carry; +#elif defined(_MSC_VER) && defined(_M_X64) + auto carry = _addcarry_u64(0, lo_, n, &lo_); + _addcarry_u64(carry, hi_, 0, &hi_); +#else + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); +#endif + return *this; } }; + +using uint128_t = conditional_t; + #ifdef UINTPTR_MAX using uintptr_t = ::uintptr_t; -inline auto to_uintptr(const void* p) -> uintptr_t { - return bit_cast(p); -} #else -using uintptr_t = fallback_uintptr; -inline auto to_uintptr(const void* p) -> fallback_uintptr { - return fallback_uintptr(p); -} +using uintptr_t = uint128_t; #endif // Returns the largest possible value for type T. Same as @@ -327,17 +441,57 @@ template constexpr auto num_bits() -> int { return std::numeric_limits::digits; } // std::numeric_limits::digits may return 0 for 128-bit ints. -template <> constexpr auto num_bits() -> int { return 128; } -template <> constexpr auto num_bits() -> int { return 128; } -template <> constexpr auto num_bits() -> int { - return static_cast(sizeof(void*) * - std::numeric_limits::digits); +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } + +// A heterogeneous bit_cast used for converting 96-bit long double to uint128_t +// and 128-bit pointers to uint128_fallback. +template sizeof(From))> +inline auto bit_cast(const From& from) -> To { + constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned)); + struct data_t { + unsigned value[static_cast(size)]; + } data = bit_cast(from); + auto result = To(); + if (const_check(is_big_endian())) { + for (int i = 0; i < size; ++i) + result = (result << num_bits()) | data.value[i]; + } else { + for (int i = size - 1; i >= 0; --i) + result = (result << num_bits()) | data.value[i]; + } + return result; +} + +template +FMT_CONSTEXPR20 inline auto countl_zero_fallback(UInt n) -> int { + int lz = 0; + constexpr UInt msb_mask = static_cast(1) << (num_bits() - 1); + for (; (n & msb_mask) == 0; n <<= 1) lz++; + return lz; +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZ(n); +#endif + return countl_zero_fallback(n); +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZLL(n); +#endif + return countl_zero_fallback(n); } FMT_INLINE void assume(bool condition) { (void)condition; -#if FMT_HAS_BUILTIN(__builtin_assume) +#if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION __builtin_assume(condition); +#elif FMT_GCC_VERSION + if (!condition) __builtin_unreachable(); #endif } @@ -356,37 +510,24 @@ inline auto get_data(Container& c) -> typename Container::value_type* { return c.data(); } -#if defined(_SECURE_SCL) && _SECURE_SCL -// Make a checked iterator to avoid MSVC warnings. -template using checked_ptr = stdext::checked_array_iterator; -template -constexpr auto make_checked(T* p, size_t size) -> checked_ptr { - return {p, size}; -} -#else -template using checked_ptr = T*; -template constexpr auto make_checked(T* p, size_t) -> T* { - return p; -} -#endif - // Attempts to reserve space for n extra characters in the output range. // Returns a pointer to the reserved range or a reference to it. -template ::value)> +template ::value&& + is_contiguous::value)> #if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION __attribute__((no_sanitize("undefined"))) #endif inline auto -reserve(std::back_insert_iterator it, size_t n) - -> checked_ptr { - Container& c = get_container(it); +reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* { + auto& c = get_container(it); size_t size = c.size(); c.resize(size + n); - return make_checked(get_data(c) + size, n); + return get_data(c) + size; } template -inline auto reserve(buffer_appender it, size_t n) -> buffer_appender { +inline auto reserve(basic_appender it, size_t n) -> basic_appender { buffer& buf = get_container(it); buf.try_reserve(buf.size() + n); return it; @@ -405,7 +546,7 @@ template constexpr auto to_pointer(OutputIt, size_t) -> T* { return nullptr; } -template auto to_pointer(buffer_appender it, size_t n) -> T* { +template auto to_pointer(basic_appender it, size_t n) -> T* { buffer& buf = get_container(it); auto size = buf.size(); if (buf.capacity() < size + n) return nullptr; @@ -413,10 +554,12 @@ template auto to_pointer(buffer_appender it, size_t n) -> T* { return buf.data() + size; } -template ::value)> -inline auto base_iterator(std::back_insert_iterator& it, - checked_ptr) - -> std::back_insert_iterator { +template ::value&& + is_contiguous::value)> +inline auto base_iterator(OutputIt it, + typename OutputIt::container_type::value_type*) + -> OutputIt { return it; } @@ -442,16 +585,10 @@ FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { return out + count; } -#ifdef __cpp_char8_t -using char8_type = char8_t; -#else -enum char8_type : unsigned char {}; -#endif - template -FMT_CONSTEXPR FMT_NOINLINE auto copy_str_noinline(InputIt begin, InputIt end, - OutputIt out) -> OutputIt { - return copy_str(begin, end, out); +FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, + OutputIt out) -> OutputIt { + return copy(begin, end, out); } // A public domain branchless UTF-8 decoder by Christopher Wellons: @@ -478,19 +615,24 @@ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) constexpr const int shiftc[] = {0, 18, 12, 6, 0}; constexpr const int shifte[] = {0, 6, 4, 2, 0}; - int len = code_point_length(s); - const char* next = s + len; + int len = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" + [static_cast(*s) >> 3]; + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + const char* next = s + len + !len; + + using uchar = unsigned char; // Assume a four-byte character and load four bytes. Unused bits are // shifted out. - *c = uint32_t(s[0] & masks[len]) << 18; - *c |= uint32_t(s[1] & 0x3f) << 12; - *c |= uint32_t(s[2] & 0x3f) << 6; - *c |= uint32_t(s[3] & 0x3f) << 0; + *c = uint32_t(uchar(s[0]) & masks[len]) << 18; + *c |= uint32_t(uchar(s[1]) & 0x3f) << 12; + *c |= uint32_t(uchar(s[2]) & 0x3f) << 6; + *c |= uint32_t(uchar(s[3]) & 0x3f) << 0; *c >>= shiftc[len]; // Accumulate the various error conditions. - using uchar = unsigned char; *e = (*c < mins[len]) << 6; // non-canonical encoding *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? *e |= (*c > 0x10FFFF) << 8; // out of range? @@ -503,7 +645,7 @@ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) return next; } -enum { invalid_code_point = ~uint32_t() }; +constexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t(); // Invokes f(cp, sv) for every code point cp in s with sv being the string view // corresponding to the code point. cp is invalid_code_point on error. @@ -514,8 +656,8 @@ FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { auto error = 0; auto end = utf8_decode(buf_ptr, &cp, &error); bool result = f(error ? invalid_code_point : cp, - string_view(ptr, to_unsigned(end - buf_ptr))); - return result ? end : nullptr; + string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr))); + return result ? (error ? buf_ptr + 1 : end) : nullptr; }; auto p = s.data(); const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. @@ -527,7 +669,7 @@ FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { } if (auto num_chars_left = s.data() + s.size() - p) { char buf[2 * block_size - 1] = {}; - copy_str(p, p + num_chars_left, buf); + copy(p, p + num_chars_left, buf); const char* buf_ptr = buf; do { auto end = decode(buf_ptr, p); @@ -544,7 +686,7 @@ inline auto compute_width(basic_string_view s) -> size_t { } // Computes approximate display width of a UTF-8 string. -FMT_CONSTEXPR inline size_t compute_width(string_view s) { +FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t { size_t num_code_points = 0; // It is not a lambda for compatibility with C++14. struct count_code_points { @@ -573,15 +715,11 @@ FMT_CONSTEXPR inline size_t compute_width(string_view s) { return true; } }; + // We could avoid branches by using utf8_decode directly. for_each_codepoint(s, count_code_points{&num_code_points}); return num_code_points; } -inline auto compute_width(basic_string_view s) -> size_t { - return compute_width(basic_string_view( - reinterpret_cast(s.data()), s.size())); -} - template inline auto code_point_index(basic_string_view s, size_t n) -> size_t { size_t size = s.size(); @@ -589,80 +727,112 @@ inline auto code_point_index(basic_string_view s, size_t n) -> size_t { } // Calculates the index of the nth code point in a UTF-8 string. -inline auto code_point_index(basic_string_view s, size_t n) - -> size_t { - const char8_type* data = s.data(); - size_t num_code_points = 0; - for (size_t i = 0, size = s.size(); i != size; ++i) { - if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i; - } - return s.size(); +inline auto code_point_index(string_view s, size_t n) -> size_t { + size_t result = s.size(); + const char* begin = s.begin(); + for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) { + if (n != 0) { + --n; + return true; + } + result = to_unsigned(sv.begin() - begin); + return false; + }); + return result; } +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; + +template +using is_signed = + std::integral_constant::is_signed || + std::is_same::value>; + +template +using is_integer = + bool_constant::value && !std::is_same::value && + !std::is_same::value && + !std::is_same::value>; + +#ifndef FMT_USE_FLOAT +# define FMT_USE_FLOAT 1 +#endif +#ifndef FMT_USE_DOUBLE +# define FMT_USE_DOUBLE 1 +#endif +#ifndef FMT_USE_LONG_DOUBLE +# define FMT_USE_LONG_DOUBLE 1 +#endif + +#if defined(FMT_USE_FLOAT128) +// Use the provided definition. +#elif FMT_CLANG_VERSION && FMT_HAS_INCLUDE() +# define FMT_USE_FLOAT128 1 +#elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \ + !defined(__STRICT_ANSI__) +# define FMT_USE_FLOAT128 1 +#else +# define FMT_USE_FLOAT128 0 +#endif +#if FMT_USE_FLOAT128 +using float128 = __float128; +#else +using float128 = void; +#endif + +template using is_float128 = std::is_same; + +template +using is_floating_point = + bool_constant::value || is_float128::value>; + template ::value> struct is_fast_float : bool_constant::is_iec559 && sizeof(T) <= sizeof(double)> {}; template struct is_fast_float : std::false_type {}; +template +using is_double_double = bool_constant::digits == 106>; + #ifndef FMT_USE_FULL_CACHE_DRAGONBOX # define FMT_USE_FULL_CACHE_DRAGONBOX 0 #endif -template -template -void buffer::append(const U* begin, const U* end) { - while (begin != end) { - auto count = to_unsigned(end - begin); - try_reserve(size_ + count); - auto free_cap = capacity_ - size_; - if (free_cap < count) count = free_cap; - std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count)); - size_ += count; - begin += count; - } -} - template struct is_locale : std::false_type {}; template struct is_locale> : std::true_type {}; } // namespace detail -FMT_MODULE_EXPORT_BEGIN +FMT_BEGIN_EXPORT // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. enum { inline_buffer_size = 500 }; /** - \rst - A dynamically growing memory buffer for trivially copyable/constructible types - with the first ``SIZE`` elements stored in the object itself. - - You can use the ``memory_buffer`` type alias for ``char`` instead. - - **Example**:: - - auto out = fmt::memory_buffer(); - format_to(std::back_inserter(out), "The answer is {}.", 42); - - This will append the following output to the ``out`` object: - - .. code-block:: none - - The answer is 42. - - The output can be converted to an ``std::string`` with ``to_string(out)``. - \endrst + * A dynamically growing memory buffer for trivially copyable/constructible + * types with the first `SIZE` elements stored in the object itself. Most + * commonly used via the `memory_buffer` alias for `char`. + * + * **Example**: + * + * auto out = fmt::memory_buffer(); + * fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); + * + * This will append "The answer is 42." to `out`. The buffer content can be + * converted to `std::string` with `to_string(out)`. */ template > -class basic_memory_buffer final : public detail::buffer { +class basic_memory_buffer : public detail::buffer { private: T store_[SIZE]; - // Don't inherit from Allocator avoid generating type_info for it. - Allocator alloc_; + // Don't inherit from Allocator to avoid generating type_info for it. + FMT_NO_UNIQUE_ADDRESS Allocator alloc_; // Deallocate memory allocated by the buffer. FMT_CONSTEXPR20 void deallocate() { @@ -670,8 +840,29 @@ class basic_memory_buffer final : public detail::buffer { if (data != store_) alloc_.deallocate(data, this->capacity()); } - protected: - void grow(size_t size) override; + static FMT_CONSTEXPR20 void grow(detail::buffer& buf, size_t size) { + detail::abort_fuzzing_if(size > 5000); + auto& self = static_cast(buf); + const size_t max_size = + std::allocator_traits::max_size(self.alloc_); + size_t old_capacity = buf.capacity(); + size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + else if (new_capacity > max_size) + new_capacity = size > max_size ? size : max_size; + T* old_data = buf.data(); + T* new_data = self.alloc_.allocate(new_capacity); + // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481). + detail::assume(buf.size() <= new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + memcpy(new_data, old_data, buf.size() * sizeof(T)); + self.set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != self.store_) self.alloc_.deallocate(old_data, old_capacity); + } public: using value_type = T; @@ -679,11 +870,9 @@ class basic_memory_buffer final : public detail::buffer { FMT_CONSTEXPR20 explicit basic_memory_buffer( const Allocator& alloc = Allocator()) - : alloc_(alloc) { + : detail::buffer(grow), alloc_(alloc) { this->set(store_, SIZE); - if (detail::is_constant_evaluated()) { - detail::fill_n(store_, SIZE, T{}); - } + if (detail::is_constant_evaluated()) detail::fill_n(store_, SIZE, T()); } FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); } @@ -695,41 +884,27 @@ class basic_memory_buffer final : public detail::buffer { size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { this->set(store_, capacity); - if (detail::is_constant_evaluated()) { - detail::copy_str(other.store_, other.store_ + size, - detail::make_checked(store_, capacity)); - } else { - std::uninitialized_copy(other.store_, other.store_ + size, - detail::make_checked(store_, capacity)); - } + detail::copy(other.store_, other.store_ + size, store_); } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called // when deallocating. other.set(other.store_, 0); + other.clear(); } this->resize(size); } public: - /** - \rst - Constructs a :class:`fmt::basic_memory_buffer` object moving the content - of the other object to it. - \endrst - */ - FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) - FMT_NOEXCEPT { + /// Constructs a `basic_memory_buffer` object moving the content of the other + /// object to it. + FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept + : detail::buffer(grow) { move(other); } - /** - \rst - Moves the content of the other ``basic_memory_buffer`` object to this one. - \endrst - */ - auto operator=(basic_memory_buffer&& other) FMT_NOEXCEPT - -> basic_memory_buffer& { + /// Moves the content of the other `basic_memory_buffer` object to this one. + auto operator=(basic_memory_buffer&& other) noexcept -> basic_memory_buffer& { FMT_ASSERT(this != &other, ""); deallocate(); move(other); @@ -739,16 +914,13 @@ class basic_memory_buffer final : public detail::buffer { // Returns a copy of the allocator associated with this buffer. auto get_allocator() const -> Allocator { return alloc_; } - /** - Resizes the buffer to contain *count* elements. If T is a POD type new - elements may not be initialized. - */ + /// Resizes the buffer to contain `count` elements. If T is a POD type new + /// elements may not be initialized. FMT_CONSTEXPR20 void resize(size_t count) { this->try_resize(count); } - /** Increases the buffer capacity to *new_capacity*. */ + /// Increases the buffer capacity to `new_capacity`. void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } - // Directly append data into the buffer using detail::buffer::append; template void append(const ContiguousRange& range) { @@ -756,85 +928,39 @@ class basic_memory_buffer final : public detail::buffer { } }; -template -void basic_memory_buffer::grow(size_t size) { -#ifdef FMT_FUZZ - if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much"); -#endif - const size_t max_size = std::allocator_traits::max_size(alloc_); - size_t old_capacity = this->capacity(); - size_t new_capacity = old_capacity + old_capacity / 2; - if (size > new_capacity) - new_capacity = size; - else if (new_capacity > max_size) - new_capacity = size > max_size ? size : max_size; - T* old_data = this->data(); - T* new_data = - std::allocator_traits::allocate(alloc_, new_capacity); - // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy(old_data, old_data + this->size(), - detail::make_checked(new_data, new_capacity)); - this->set(new_data, new_capacity); - // deallocate must not throw according to the standard, but even if it does, - // the buffer already uses the new storage and will deallocate it in - // destructor. - if (old_data != store_) alloc_.deallocate(old_data, old_capacity); -} - using memory_buffer = basic_memory_buffer; template struct is_contiguous> : std::true_type { }; +FMT_END_EXPORT namespace detail { +FMT_API auto write_console(int fd, string_view text) -> bool; FMT_API void print(std::FILE*, string_view); -} +} // namespace detail + +FMT_BEGIN_EXPORT + +// Suppress a misleading warning in older versions of clang. +#if FMT_CLANG_VERSION +# pragma clang diagnostic ignored "-Wweak-vtables" +#endif -/** A formatting error such as invalid format string. */ -FMT_CLASS_API -class FMT_API format_error : public std::runtime_error { +/// An error reported from a formatting function. +class FMT_SO_VISIBILITY("default") format_error : public std::runtime_error { public: - explicit format_error(const char* message) : std::runtime_error(message) {} - explicit format_error(const std::string& message) - : std::runtime_error(message) {} - format_error(const format_error&) = default; - format_error& operator=(const format_error&) = default; - format_error(format_error&&) = default; - format_error& operator=(format_error&&) = default; - ~format_error() FMT_NOEXCEPT override FMT_MSC_DEFAULT; + using std::runtime_error::runtime_error; }; -/** - \rst - Constructs a `~fmt::format_arg_store` object that contains references - to arguments and can be implicitly converted to `~fmt::format_args`. - If ``fmt`` is a compile-time string then `make_args_checked` checks - its validity at compile time. - \endrst - */ -template > -FMT_INLINE auto make_args_checked(const S& fmt, - const remove_reference_t&... args) - -> format_arg_store, remove_reference_t...> { - static_assert( - detail::count<( - std::is_base_of>::value && - std::is_reference::value)...>() == 0, - "passing views as lvalues is disallowed"); - detail::check_format_string(fmt); - return {args...}; -} - -// compile-time support namespace detail_exported { -#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +#if FMT_USE_NONTYPE_TEMPLATE_ARGS template struct fixed_string { constexpr fixed_string(const Char (&str)[N]) { - detail::copy_str(static_cast(str), - str + N, data); + detail::copy(static_cast(str), + str + N, data); } - Char data[N]{}; + Char data[N] = {}; }; #endif @@ -847,39 +973,123 @@ constexpr auto compile_string_to_view(const Char (&s)[N]) return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; } template -constexpr auto compile_string_to_view(detail::std_string_view s) +constexpr auto compile_string_to_view(basic_string_view s) -> basic_string_view { - return {s.data(), s.size()}; + return s; } } // namespace detail_exported -FMT_BEGIN_DETAIL_NAMESPACE +// A generic formatting context with custom output iterator and character +// (code unit) support. Char is the format string code unit type which can be +// different from OutputIt::value_type. +template class generic_context { + private: + OutputIt out_; + basic_format_args args_; + detail::locale_ref loc_; -template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; -template <> struct is_integral : std::true_type {}; + public: + using char_type = Char; + using iterator = OutputIt; + using parse_context_type = basic_format_parse_context; + template using formatter_type = formatter; + + constexpr generic_context(OutputIt out, + basic_format_args ctx_args, + detail::locale_ref loc = {}) + : out_(out), args_(ctx_args), loc_(loc) {} + generic_context(generic_context&&) = default; + generic_context(const generic_context&) = delete; + void operator=(const generic_context&) = delete; + + constexpr auto arg(int id) const -> basic_format_arg { + return args_.get(id); + } + auto arg(basic_string_view name) -> basic_format_arg { + return args_.get(name); + } + FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { + return args_.get_id(name); + } + auto args() const -> const basic_format_args& { + return args_; + } -template -using is_signed = - std::integral_constant::is_signed || - std::is_same::value>; + FMT_CONSTEXPR auto out() -> iterator { return out_; } + + void advance_to(iterator it) { + if (!detail::is_back_insert_iterator()) out_ = it; + } + + FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } +}; + +class loc_value { + private: + basic_format_arg value_; + + public: + template ::value)> + loc_value(T value) : value_(detail::make_arg(value)) {} + + template ::value)> + loc_value(T) {} + + template auto visit(Visitor&& vis) -> decltype(vis(0)) { + return value_.visit(vis); + } +}; + +// A locale facet that formats values in UTF-8. +// It is parameterized on the locale to avoid the heavy include. +template class format_facet : public Locale::facet { + private: + std::string separator_; + std::string grouping_; + std::string decimal_point_; + + protected: + virtual auto do_put(appender out, loc_value val, + const format_specs& specs) const -> bool; + + public: + static FMT_API typename Locale::id id; + + explicit format_facet(Locale& loc); + explicit format_facet(string_view sep = "", + std::initializer_list g = {3}, + std::string decimal_point = ".") + : separator_(sep.data(), sep.size()), + grouping_(g.begin(), g.end()), + decimal_point_(decimal_point) {} + + auto put(appender out, loc_value val, const format_specs& specs) const + -> bool { + return do_put(out, val, specs); + } +}; + +FMT_END_EXPORT + +namespace detail { // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. template ::value)> -FMT_CONSTEXPR auto is_negative(T value) -> bool { +constexpr auto is_negative(T value) -> bool { return value < 0; } template ::value)> -FMT_CONSTEXPR auto is_negative(T) -> bool { +constexpr auto is_negative(T) -> bool { return false; } -template ::value)> -FMT_CONSTEXPR auto is_supported_floating_point(T) -> uint16_t { - return (std::is_same::value && FMT_USE_FLOAT) || - (std::is_same::value && FMT_USE_DOUBLE) || - (std::is_same::value && FMT_USE_LONG_DOUBLE); +template +FMT_CONSTEXPR auto is_supported_floating_point(T) -> bool { + if (std::is_same()) return FMT_USE_FLOAT; + if (std::is_same()) return FMT_USE_DOUBLE; + if (std::is_same()) return FMT_USE_LONG_DOUBLE; + return true; } // Smallest of uint32_t, uint64_t, uint128_t that is large enough to @@ -892,13 +1102,13 @@ using uint32_or_64_or_128_t = template using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; -#define FMT_POWERS_OF_10(factor) \ - factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ - (factor)*1000000, (factor)*10000000, (factor)*100000000, \ - (factor)*1000000000 +#define FMT_POWERS_OF_10(factor) \ + factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, \ + (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \ + (factor) * 100000000, (factor) * 1000000000 // Converts value in the range [0, 100) to a string. -constexpr const char* digits2(size_t value) { +constexpr auto digits2(size_t value) -> const char* { // GCC generates slightly better code when value is pointer-size. return &"0001020304050607080910111213141516171819" "2021222324252627282930313233343536373839" @@ -908,8 +1118,8 @@ constexpr const char* digits2(size_t value) { } // Sign is a template parameter to workaround a bug in gcc 4.8. -template constexpr Char sign(Sign s) { -#if !FMT_GCC_VERSION || FMT_GCC_VERSION > 408 +template constexpr auto sign(Sign s) -> Char { +#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 604 static_assert(std::is_same::value, ""); #endif return static_cast("\0-+ "[s]); @@ -930,7 +1140,7 @@ template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { } } #if FMT_USE_INT128 -FMT_CONSTEXPR inline auto count_digits(uint128_t n) -> int { +FMT_CONSTEXPR inline auto count_digits(uint128_opt n) -> int { return count_digits_fallback(n); } #endif @@ -960,9 +1170,7 @@ inline auto do_count_digits(uint64_t n) -> int { // except for n == 0 in which case count_digits returns 1. FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { #ifdef FMT_BUILTIN_CLZLL - if (!is_constant_evaluated()) { - return do_count_digits(n); - } + if (!is_constant_evaluated()) return do_count_digits(n); #endif return count_digits_fallback(n); } @@ -971,25 +1179,26 @@ FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { template FMT_CONSTEXPR auto count_digits(UInt n) -> int { #ifdef FMT_BUILTIN_CLZ - if (num_bits() == 32) + if (!is_constant_evaluated() && num_bits() == 32) return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; #endif - int num_digits = 0; - do { - ++num_digits; - } while ((n >>= BITS) != 0); - return num_digits; + // Lambda avoids unreachable code warnings from NVHPC. + return [](UInt m) { + int num_digits = 0; + do { + ++num_digits; + } while ((m >>= BITS) != 0); + return num_digits; + }(n); } -template <> auto count_digits<4>(detail::fallback_uintptr n) -> int; - #ifdef FMT_BUILTIN_CLZ // It is a separate function rather than a part of count_digits to workaround // the lack of static constexpr in constexpr functions. FMT_INLINE auto do_count_digits(uint32_t n) -> int { // An optimization by Kendall Willets from https://bit.ly/3uOIQrB. // This increments the upper 32 bits (log10(T) - 1) when >= T is added. -# define FMT_INC(T) (((sizeof(# T) - 1ull) << 32) - T) +# define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) static constexpr uint64_t table[] = { FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 @@ -1018,15 +1227,11 @@ FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { return count_digits_fallback(n); } -template constexpr auto digits10() FMT_NOEXCEPT -> int { +template constexpr auto digits10() noexcept -> int { return std::numeric_limits::digits10; } -template <> constexpr auto digits10() FMT_NOEXCEPT -> int { - return 38; -} -template <> constexpr auto digits10() FMT_NOEXCEPT -> int { - return 38; -} +template <> constexpr auto digits10() noexcept -> int { return 38; } +template <> constexpr auto digits10() noexcept -> int { return 38; } template struct thousands_sep_result { std::string grouping; @@ -1106,12 +1311,12 @@ FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size) template >::value)> -inline auto format_decimal(Iterator out, UInt value, int size) +FMT_CONSTEXPR inline auto format_decimal(Iterator out, UInt value, int size) -> format_decimal_result { // Buffer is large enough to hold all digits (digits10 + 1). - Char buffer[digits10() + 1]; + Char buffer[digits10() + 1] = {}; auto end = format_decimal(buffer, value, size).end; - return {out, detail::copy_str_noinline(buffer, end, out)}; + return {out, detail::copy_noinline(buffer, end, out)}; } template @@ -1121,46 +1326,24 @@ FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits, Char* end = buffer; do { const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; - unsigned digit = (value & ((1 << BASE_BITS) - 1)); + unsigned digit = static_cast(value & ((1 << BASE_BITS) - 1)); *--buffer = static_cast(BASE_BITS < 4 ? static_cast('0' + digit) : digits[digit]); } while ((value >>= BASE_BITS) != 0); return end; } -template -auto format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits, - bool = false) -> Char* { - auto char_digits = std::numeric_limits::digits / 4; - int start = (num_digits + char_digits - 1) / char_digits - 1; - if (int start_digits = num_digits % char_digits) { - unsigned value = n.value[start--]; - buffer = format_uint(buffer, value, start_digits); - } - for (; start >= 0; --start) { - unsigned value = n.value[start]; - buffer += char_digits; - auto p = buffer; - for (int i = 0; i < char_digits; ++i) { - unsigned digit = (value & ((1 << BASE_BITS) - 1)); - *--p = static_cast("0123456789abcdef"[digit]); - value >>= BASE_BITS; - } - } - return buffer; -} - template -inline auto format_uint(It out, UInt value, int num_digits, bool upper = false) - -> It { +FMT_CONSTEXPR inline auto format_uint(It out, UInt value, int num_digits, + bool upper = false) -> It { if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { format_uint(ptr, value, num_digits, upper); return out; } // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). - char buffer[num_bits() / BASE_BITS + 1]; + char buffer[num_bits() / BASE_BITS + 1] = {}; format_uint(buffer, value, num_digits, upper); - return detail::copy_str_noinline(buffer, buffer + num_digits, out); + return detail::copy_noinline(buffer, buffer + num_digits, out); } // A converter from UTF-8 to UTF-16. @@ -1176,61 +1359,181 @@ class utf8_to_utf16 { auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; -namespace dragonbox { +enum class to_utf8_error_policy { abort, replace }; -// Type-specific information that Dragonbox uses. -template struct float_info; +// A converter from UTF-16/UTF-32 (host endian) to UTF-8. +template class to_utf8 { + private: + Buffer buffer_; -template <> struct float_info { - using carrier_uint = uint32_t; - static const int significand_bits = 23; - static const int exponent_bits = 8; - static const int min_exponent = -126; - static const int max_exponent = 127; - static const int exponent_bias = -127; - static const int decimal_digits = 9; - static const int kappa = 1; - static const int big_divisor = 100; - static const int small_divisor = 10; - static const int min_k = -31; - static const int max_k = 46; - static const int cache_bits = 64; - static const int divisibility_check_by_5_threshold = 39; - static const int case_fc_pm_half_lower_threshold = -1; - static const int case_fc_pm_half_upper_threshold = 6; - static const int case_fc_lower_threshold = -2; - static const int case_fc_upper_threshold = 6; - static const int case_shorter_interval_left_endpoint_lower_threshold = 2; - static const int case_shorter_interval_left_endpoint_upper_threshold = 3; - static const int shorter_interval_tie_lower_threshold = -35; - static const int shorter_interval_tie_upper_threshold = -35; - static const int max_trailing_zeros = 7; + public: + to_utf8() {} + explicit to_utf8(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) { + static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4, + "Expect utf16 or utf32"); + if (!convert(s, policy)) + FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? "invalid utf16" + : "invalid utf32")); + } + operator string_view() const { return string_view(&buffer_[0], size()); } + auto size() const -> size_t { return buffer_.size() - 1; } + auto c_str() const -> const char* { return &buffer_[0]; } + auto str() const -> std::string { return std::string(&buffer_[0], size()); } + + // Performs conversion returning a bool instead of throwing exception on + // conversion error. This method may still throw in case of memory allocation + // error. + auto convert(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { + if (!convert(buffer_, s, policy)) return false; + buffer_.push_back(0); + return true; + } + static auto convert(Buffer& buf, basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { + for (auto p = s.begin(); p != s.end(); ++p) { + uint32_t c = static_cast(*p); + if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { + // Handle a surrogate pair. + ++p; + if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { + if (policy == to_utf8_error_policy::abort) return false; + buf.append(string_view("\xEF\xBF\xBD")); + --p; + } else { + c = (c << 10) + static_cast(*p) - 0x35fdc00; + } + } else if (c < 0x80) { + buf.push_back(static_cast(c)); + } else if (c < 0x800) { + buf.push_back(static_cast(0xc0 | (c >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { + buf.push_back(static_cast(0xe0 | (c >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if (c >= 0x10000 && c <= 0x10ffff) { + buf.push_back(static_cast(0xf0 | (c >> 18))); + buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else { + return false; + } + } + return true; + } }; -template <> struct float_info { - using carrier_uint = uint64_t; - static const int significand_bits = 52; - static const int exponent_bits = 11; - static const int min_exponent = -1022; - static const int max_exponent = 1023; - static const int exponent_bias = -1023; - static const int decimal_digits = 17; - static const int kappa = 2; - static const int big_divisor = 1000; - static const int small_divisor = 100; - static const int min_k = -292; - static const int max_k = 326; - static const int cache_bits = 128; - static const int divisibility_check_by_5_threshold = 86; - static const int case_fc_pm_half_lower_threshold = -2; - static const int case_fc_pm_half_upper_threshold = 9; - static const int case_fc_lower_threshold = -4; - static const int case_fc_upper_threshold = 9; - static const int case_shorter_interval_left_endpoint_lower_threshold = 2; - static const int case_shorter_interval_left_endpoint_upper_threshold = 3; - static const int shorter_interval_tie_lower_threshold = -77; +// Computes 128-bit result of multiplication of two 64-bit unsigned integers. +inline auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return {static_cast(p >> 64), static_cast(p)}; +#elif defined(_MSC_VER) && defined(_M_X64) + auto hi = uint64_t(); + auto lo = _umul128(x, y, &hi); + return {hi, lo}; +#else + const uint64_t mask = static_cast(max_value()); + + uint64_t a = x >> 32; + uint64_t b = x & mask; + uint64_t c = y >> 32; + uint64_t d = y & mask; + + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + + uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + (bd & mask)}; +#endif +} + +namespace dragonbox { +// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from +// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. +inline auto floor_log10_pow2(int e) noexcept -> int { + FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); + static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); + return (e * 315653) >> 20; +} + +inline auto floor_log2_pow10(int e) noexcept -> int { + FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); + return (e * 1741647) >> 19; +} + +// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. +inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return static_cast(p >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + return __umulh(x, y); +#else + return umul128(x, y).high(); +#endif +} + +// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { + uint128_fallback r = umul128(x, y.high()); + r += umul128_upper64(x, y.low()); + return r; +} + +FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback; + +// Type-specific information that Dragonbox uses. +template struct float_info; + +template <> struct float_info { + using carrier_uint = uint32_t; + static const int exponent_bits = 8; + static const int kappa = 1; + static const int big_divisor = 100; + static const int small_divisor = 10; + static const int min_k = -31; + static const int max_k = 46; + static const int shorter_interval_tie_lower_threshold = -35; + static const int shorter_interval_tie_upper_threshold = -35; +}; + +template <> struct float_info { + using carrier_uint = uint64_t; + static const int exponent_bits = 11; + static const int kappa = 2; + static const int big_divisor = 1000; + static const int small_divisor = 100; + static const int min_k = -292; + static const int max_k = 341; + static const int shorter_interval_tie_lower_threshold = -77; static const int shorter_interval_tie_upper_threshold = -77; - static const int max_trailing_zeros = 16; +}; + +// An 80- or 128-bit floating point number. +template +struct float_info::digits == 64 || + std::numeric_limits::digits == 113 || + is_float128::value>> { + using carrier_uint = detail::uint128_t; + static const int exponent_bits = 15; +}; + +// A double-double floating point number. +template +struct float_info::value>> { + using carrier_uint = detail::uint128_t; }; template struct decimal_fp { @@ -1239,16 +1542,35 @@ template struct decimal_fp { int exponent; }; -template -FMT_API auto to_decimal(T x) FMT_NOEXCEPT -> decimal_fp; +template FMT_API auto to_decimal(T x) noexcept -> decimal_fp; } // namespace dragonbox -template +// Returns true iff Float has the implicit bit which is not stored. +template constexpr auto has_implicit_bit() -> bool { + // An 80-bit FP number has a 64-bit significand an no implicit bit. + return std::numeric_limits::digits != 64; +} + +// Returns the number of significand bits stored in Float. The implicit bit is +// not counted since it is not stored. +template constexpr auto num_significand_bits() -> int { + // std::numeric_limits may not support __float128. + return is_float128() ? 112 + : (std::numeric_limits::digits - + (has_implicit_bit() ? 1 : 0)); +} + +template constexpr auto exponent_mask() -> - typename dragonbox::float_info::carrier_uint { - using uint = typename dragonbox::float_info::carrier_uint; - return ((uint(1) << dragonbox::float_info::exponent_bits) - 1) - << dragonbox::float_info::significand_bits; + typename dragonbox::float_info::carrier_uint { + using float_uint = typename dragonbox::float_info::carrier_uint; + return ((float_uint(1) << dragonbox::float_info::exponent_bits) - 1) + << num_significand_bits(); +} +template constexpr auto exponent_bias() -> int { + // std::numeric_limits may not support __float128. + return is_float128() ? 16383 + : std::numeric_limits::max_exponent - 1; } // Writes the exponent exp in the form "[+-]d{2,3}" to buffer. @@ -1273,41 +1595,121 @@ FMT_CONSTEXPR auto write_exponent(int exp, It it) -> It { return it; } -template -FMT_HEADER_ONLY_CONSTEXPR20 auto format_float(T value, int precision, - float_specs specs, - buffer& buf) -> int; +// A floating-point number f * pow(2, e) where F is an unsigned type. +template struct basic_fp { + F f; + int e; + + static constexpr const int num_significand_bits = + static_cast(sizeof(F) * num_bits()); + + constexpr basic_fp() : f(0), e(0) {} + constexpr basic_fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} + + // Constructs fp from an IEEE754 floating-point number. + template FMT_CONSTEXPR basic_fp(Float n) { assign(n); } + + // Assigns n to this and return true iff predecessor is closer than successor. + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::digits <= 113, "unsupported FP"); + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename dragonbox::float_info::carrier_uint; + const auto num_float_significand_bits = + detail::num_significand_bits(); + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + const auto significand_mask = implicit_bit - 1; + auto u = bit_cast(n); + f = static_cast(u & significand_mask); + auto biased_e = static_cast((u & exponent_mask()) >> + num_float_significand_bits); + // The predecessor is closer if n is a normalized power of 2 (f == 0) + // other than the smallest normalized number (biased_e > 1). + auto is_predecessor_closer = f == 0 && biased_e > 1; + if (biased_e == 0) + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + else if (has_implicit_bit()) + f += static_cast(implicit_bit); + e = biased_e - exponent_bias() - num_float_significand_bits; + if (!has_implicit_bit()) ++e; + return is_predecessor_closer; + } -// Formats a floating-point number with snprintf. -template -auto snprintf_float(T value, int precision, float_specs specs, - buffer& buf) -> int; + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::is_iec559, "unsupported FP"); + return assign(static_cast(n)); + } +}; -template constexpr auto promote_float(T value) -> T { +using fp = basic_fp; + +// Normalizes the value converted from double and multiplied by (1 << SHIFT). +template +FMT_CONSTEXPR auto normalize(basic_fp value) -> basic_fp { + // Handle subnormals. + const auto implicit_bit = F(1) << num_significand_bits(); + const auto shifted_implicit_bit = implicit_bit << SHIFT; + while ((value.f & shifted_implicit_bit) == 0) { + value.f <<= 1; + --value.e; + } + // Subtract 1 to account for hidden bit. + const auto offset = basic_fp::num_significand_bits - + num_significand_bits() - SHIFT - 1; + value.f <<= offset; + value.e -= offset; return value; } -constexpr auto promote_float(float value) -> double { - return static_cast(value); + +// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. +FMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t { +#if FMT_USE_INT128 + auto product = static_cast<__uint128_t>(lhs) * rhs; + auto f = static_cast(product >> 64); + return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; +#else + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = lhs >> 32, b = lhs & mask; + uint64_t c = rhs >> 32, d = rhs & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); +#endif +} + +FMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp { + return {multiply(x.f, y.f), x.e + y.e + 64}; } -template -FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, - const fill_t& fill) -> OutputIt { +template () == num_bits()> +using convert_float_result = + conditional_t::value || doublish, double, T>; + +template +constexpr auto convert_float(T value) -> convert_float_result { + return static_cast>(value); +} + +template +FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, const fill_t& fill) + -> OutputIt { auto fill_size = fill.size(); - if (fill_size == 1) return detail::fill_n(it, n, fill[0]); - auto data = fill.data(); - for (size_t i = 0; i < n; ++i) - it = copy_str(data, data + fill_size, it); + if (fill_size == 1) return detail::fill_n(it, n, fill.template get()); + if (const Char* data = fill.template data()) { + for (size_t i = 0; i < n; ++i) it = copy(data, data + fill_size, it); + } return it; } // Writes the output of f, padded according to format specifications in specs. // size: output size in code units. // width: output display width in (terminal) column positions. -template -FMT_CONSTEXPR auto write_padded(OutputIt out, - const basic_format_specs& specs, +FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, size_t size, size_t width, F&& f) -> OutputIt { static_assert(align == align::left || align == align::right, ""); unsigned spec_width = to_unsigned(specs.width); @@ -1318,33 +1720,32 @@ FMT_CONSTEXPR auto write_padded(OutputIt out, size_t left_padding = padding >> shifts[specs.align]; size_t right_padding = padding - left_padding; auto it = reserve(out, size + padding * specs.fill.size()); - if (left_padding != 0) it = fill(it, left_padding, specs.fill); + if (left_padding != 0) it = fill(it, left_padding, specs.fill); it = f(it); - if (right_padding != 0) it = fill(it, right_padding, specs.fill); + if (right_padding != 0) it = fill(it, right_padding, specs.fill); return base_iterator(out, it); } -template -constexpr auto write_padded(OutputIt out, const basic_format_specs& specs, +constexpr auto write_padded(OutputIt out, const format_specs& specs, size_t size, F&& f) -> OutputIt { - return write_padded(out, specs, size, size, f); + return write_padded(out, specs, size, size, f); } -template +template FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, - const basic_format_specs& specs) - -> OutputIt { - return write_padded( + const format_specs& specs = {}) -> OutputIt { + return write_padded( out, specs, bytes.size(), [bytes](reserve_iterator it) { const char* data = bytes.data(); - return copy_str(data, data + bytes.size(), it); + return copy(data, data + bytes.size(), it); }); } template -auto write_ptr(OutputIt out, UIntPtr value, - const basic_format_specs* specs) -> OutputIt { +auto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs) + -> OutputIt { int num_digits = count_digits<4>(value); auto size = to_unsigned(num_digits) + size_t(2); auto write = [=](reserve_iterator it) { @@ -1352,26 +1753,176 @@ auto write_ptr(OutputIt out, UIntPtr value, *it++ = static_cast('x'); return format_uint<4, Char>(it, value, num_digits); }; - return specs ? write_padded(out, *specs, size, write) + return specs ? write_padded(out, *specs, size, write) : base_iterator(out, write(reserve(out, size))); } +// Returns true iff the code point cp is printable. +FMT_API auto is_printable(uint32_t cp) -> bool; + +inline auto needs_escape(uint32_t cp) -> bool { + return cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\' || + !is_printable(cp); +} + +template struct find_escape_result { + const Char* begin; + const Char* end; + uint32_t cp; +}; + +template +auto find_escape(const Char* begin, const Char* end) + -> find_escape_result { + for (; begin != end; ++begin) { + uint32_t cp = static_cast>(*begin); + if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; + if (needs_escape(cp)) return {begin, begin + 1, cp}; + } + return {begin, nullptr, 0}; +} + +inline auto find_escape(const char* begin, const char* end) + -> find_escape_result { + if (!use_utf8()) return find_escape(begin, end); + auto result = find_escape_result{end, nullptr, 0}; + for_each_codepoint(string_view(begin, to_unsigned(end - begin)), + [&](uint32_t cp, string_view sv) { + if (needs_escape(cp)) { + result = {sv.begin(), sv.end(), cp}; + return false; + } + return true; + }); + return result; +} + +#define FMT_STRING_IMPL(s, base, explicit) \ + [] { \ + /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ + /* Use a macro-like name to avoid shadowing warnings. */ \ + struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ + using char_type FMT_MAYBE_UNUSED = fmt::remove_cvref_t; \ + FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \ + operator fmt::basic_string_view() const { \ + return fmt::detail_exported::compile_string_to_view(s); \ + } \ + }; \ + return FMT_COMPILE_STRING(); \ + }() + +/** + * Constructs a compile-time format string from a string literal `s`. + * + * **Example**: + * + * // A compile-time error because 'd' is an invalid specifier for strings. + * std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); + */ +#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string, ) + +template +auto write_codepoint(OutputIt out, char prefix, uint32_t cp) -> OutputIt { + *out++ = static_cast('\\'); + *out++ = static_cast(prefix); + Char buf[width]; + fill_n(buf, width, static_cast('0')); + format_uint<4>(buf, cp, width); + return copy(buf, buf + width, out); +} + +template +auto write_escaped_cp(OutputIt out, const find_escape_result& escape) + -> OutputIt { + auto c = static_cast(escape.cp); + switch (escape.cp) { + case '\n': + *out++ = static_cast('\\'); + c = static_cast('n'); + break; + case '\r': + *out++ = static_cast('\\'); + c = static_cast('r'); + break; + case '\t': + *out++ = static_cast('\\'); + c = static_cast('t'); + break; + case '"': + FMT_FALLTHROUGH; + case '\'': + FMT_FALLTHROUGH; + case '\\': + *out++ = static_cast('\\'); + break; + default: + if (escape.cp < 0x100) return write_codepoint<2, Char>(out, 'x', escape.cp); + if (escape.cp < 0x10000) + return write_codepoint<4, Char>(out, 'u', escape.cp); + if (escape.cp < 0x110000) + return write_codepoint<8, Char>(out, 'U', escape.cp); + for (Char escape_char : basic_string_view( + escape.begin, to_unsigned(escape.end - escape.begin))) { + out = write_codepoint<2, Char>(out, 'x', + static_cast(escape_char) & 0xFF); + } + return out; + } + *out++ = c; + return out; +} + template -FMT_CONSTEXPR auto write_char(OutputIt out, Char value, - const basic_format_specs& specs) +auto write_escaped_string(OutputIt out, basic_string_view str) -> OutputIt { - return write_padded(out, specs, 1, [=](reserve_iterator it) { + *out++ = static_cast('"'); + auto begin = str.begin(), end = str.end(); + do { + auto escape = find_escape(begin, end); + out = copy(begin, escape.begin, out); + begin = escape.end; + if (!begin) break; + out = write_escaped_cp(out, escape); + } while (begin != end); + *out++ = static_cast('"'); + return out; +} + +template +auto write_escaped_char(OutputIt out, Char v) -> OutputIt { + Char v_array[1] = {v}; + *out++ = static_cast('\''); + if ((needs_escape(static_cast(v)) && v != static_cast('"')) || + v == static_cast('\'')) { + out = write_escaped_cp(out, + find_escape_result{v_array, v_array + 1, + static_cast(v)}); + } else { + *out++ = v; + } + *out++ = static_cast('\''); + return out; +} + +template +FMT_CONSTEXPR auto write_char(OutputIt out, Char value, + const format_specs& specs) -> OutputIt { + bool is_debug = specs.type == presentation_type::debug; + return write_padded(out, specs, 1, [=](reserve_iterator it) { + if (is_debug) return write_escaped_char(it, value); *it++ = value; return it; }); } template -FMT_CONSTEXPR auto write(OutputIt out, Char value, - const basic_format_specs& specs, +FMT_CONSTEXPR auto write(OutputIt out, Char value, const format_specs& specs, locale_ref loc = {}) -> OutputIt { + // char is formatted as unsigned char for consistency across platforms. + using unsigned_type = + conditional_t::value, unsigned char, unsigned>; return check_char_specs(specs) - ? write_char(out, value, specs) - : write(out, static_cast(value), specs, loc); + ? write_char(out, value, specs) + : write(out, static_cast(value), specs, loc); } // Data for write_int that doesn't depend on output iterator type. It is used to @@ -1381,7 +1932,7 @@ template struct write_int_data { size_t padding; FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, - const basic_format_specs& specs) + const format_specs& specs) : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { if (specs.align == align::numeric) { auto width = to_unsigned(specs.width); @@ -1400,10 +1951,10 @@ template struct write_int_data { // // where are written by write_digits(it). // prefix contains chars in three lower bytes and the size in the fourth byte. -template +template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, unsigned prefix, - const basic_format_specs& specs, + const format_specs& specs, W write_digits) -> OutputIt { // Slightly faster check for specs.width == 0 && specs.precision == -1. if ((specs.width | (specs.precision + 1)) == 0) { @@ -1415,7 +1966,7 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, return base_iterator(out, write_digits(it)); } auto data = write_int_data(num_digits, prefix, specs); - return write_padded( + return write_padded( out, specs, data.size, [=](reserve_iterator it) { for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); @@ -1426,19 +1977,19 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, template class digit_grouping { private: - thousands_sep_result sep_; + std::string grouping_; + std::basic_string thousands_sep_; struct next_state { std::string::const_iterator group; int pos; }; - next_state initial_state() const { return {sep_.grouping.begin(), 0}; } + auto initial_state() const -> next_state { return {grouping_.begin(), 0}; } // Returns the next digit group separator position. - int next(next_state& state) const { - if (!sep_.thousands_sep) return max_value(); - if (state.group == sep_.grouping.end()) - return state.pos += sep_.grouping.back(); + auto next(next_state& state) const -> int { + if (thousands_sep_.empty()) return max_value(); + if (state.group == grouping_.end()) return state.pos += grouping_.back(); if (*state.group <= 0 || *state.group == max_value()) return max_value(); state.pos += *state.group++; @@ -1447,16 +1998,17 @@ template class digit_grouping { public: explicit digit_grouping(locale_ref loc, bool localized = true) { - if (localized) - sep_ = thousands_sep(loc); - else - sep_.thousands_sep = Char(); + if (!localized) return; + auto sep = thousands_sep(loc); + grouping_ = sep.grouping; + if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); } - explicit digit_grouping(thousands_sep_result sep) : sep_(sep) {} + digit_grouping(std::string grouping, std::basic_string sep) + : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} - Char separator() const { return sep_.thousands_sep; } + auto has_separator() const -> bool { return !thousands_sep_.empty(); } - int count_separators(int num_digits) const { + auto count_separators(int num_digits) const -> int { int count = 0; auto state = initial_state(); while (num_digits > next(state)) ++count; @@ -1465,7 +2017,7 @@ template class digit_grouping { // Applies grouping to digits and write the output to out. template - Out apply(Out out, basic_string_view digits) const { + auto apply(Out out, basic_string_view digits) const -> Out { auto num_digits = static_cast(digits.size()); auto separators = basic_memory_buffer(); separators.push_back(0); @@ -1477,7 +2029,8 @@ template class digit_grouping { for (int i = 0, sep_index = static_cast(separators.size() - 1); i < num_digits; ++i) { if (num_digits - i == separators[sep_index]) { - *out++ = separator(); + out = copy(thousands_sep_.data(), + thousands_sep_.data() + thousands_sep_.size(), out); --sep_index; } *out++ = static_cast(digits[to_unsigned(i)]); @@ -1486,35 +2039,69 @@ template class digit_grouping { } }; +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; +} + +// Writes a decimal integer with digit grouping. template -auto write_int_localized(OutputIt out, UInt value, unsigned prefix, - const basic_format_specs& specs, - const digit_grouping& grouping) -> OutputIt { +auto write_int(OutputIt out, UInt value, unsigned prefix, + const format_specs& specs, const digit_grouping& grouping) + -> OutputIt { static_assert(std::is_same, UInt>::value, ""); - int num_digits = count_digits(value); - char digits[40]; - format_decimal(digits, value, num_digits); - unsigned size = to_unsigned((prefix != 0 ? 1 : 0) + num_digits + - grouping.count_separators(num_digits)); - return write_padded( + int num_digits = 0; + auto buffer = memory_buffer(); + switch (specs.type) { + default: + FMT_ASSERT(false, ""); + FMT_FALLTHROUGH; + case presentation_type::none: + case presentation_type::dec: + num_digits = count_digits(value); + format_decimal(appender(buffer), value, num_digits); + break; + case presentation_type::hex: + if (specs.alt) + prefix_append(prefix, unsigned(specs.upper ? 'X' : 'x') << 8 | '0'); + num_digits = count_digits<4>(value); + format_uint<4, char>(appender(buffer), value, num_digits, specs.upper); + break; + case presentation_type::oct: + num_digits = count_digits<3>(value); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + if (specs.alt && specs.precision <= num_digits && value != 0) + prefix_append(prefix, '0'); + format_uint<3, char>(appender(buffer), value, num_digits); + break; + case presentation_type::bin: + if (specs.alt) + prefix_append(prefix, unsigned(specs.upper ? 'B' : 'b') << 8 | '0'); + num_digits = count_digits<1>(value); + format_uint<1, char>(appender(buffer), value, num_digits); + break; + case presentation_type::chr: + return write_char(out, static_cast(value), specs); + } + + unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + + to_unsigned(grouping.count_separators(num_digits)); + return write_padded( out, specs, size, size, [&](reserve_iterator it) { - if (prefix != 0) *it++ = static_cast(prefix); - return grouping.apply(it, string_view(digits, to_unsigned(num_digits))); + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + return grouping.apply(it, string_view(buffer.data(), buffer.size())); }); } -template -auto write_int_localized(OutputIt& out, UInt value, unsigned prefix, - const basic_format_specs& specs, locale_ref loc) +// Writes a localized value. +FMT_API auto write_loc(appender out, loc_value value, const format_specs& specs, + locale_ref loc) -> bool; +template +inline auto write_loc(OutputIt, loc_value, const format_specs&, locale_ref) -> bool { - auto grouping = digit_grouping(loc); - out = write_int_localized(out, value, prefix, specs, grouping); - return true; -} - -FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { - prefix |= prefix != 0 ? value << 8 : value; - prefix += (1u + (value > 0xff ? 1 : 0)) << 24; + return false; } template struct write_int_arg { @@ -1538,142 +2125,315 @@ FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) return {abs_value, prefix}; } +template struct loc_writer { + basic_appender out; + const format_specs& specs; + std::basic_string sep; + std::string grouping; + std::basic_string decimal_point; + + template ::value)> + auto operator()(T value) -> bool { + auto arg = make_write_int_arg(value, specs.sign); + write_int(out, static_cast>(arg.abs_value), arg.prefix, + specs, digit_grouping(grouping, sep)); + return true; + } + + template ::value)> + auto operator()(T) -> bool { + return false; + } +}; + template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, - const basic_format_specs& specs, - locale_ref loc) -> OutputIt { + const format_specs& specs, locale_ref) + -> OutputIt { static_assert(std::is_same>::value, ""); auto abs_value = arg.abs_value; auto prefix = arg.prefix; switch (specs.type) { + default: + FMT_ASSERT(false, ""); + FMT_FALLTHROUGH; case presentation_type::none: case presentation_type::dec: { - if (specs.localized && - write_int_localized(out, static_cast>(abs_value), - prefix, specs, loc)) { - return out; - } - auto num_digits = count_digits(abs_value); - return write_int( + int num_digits = count_digits(abs_value); + return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { return format_decimal(it, abs_value, num_digits).end; }); } - case presentation_type::hex_lower: - case presentation_type::hex_upper: { - bool upper = specs.type == presentation_type::hex_upper; + case presentation_type::hex: { if (specs.alt) - prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0'); + prefix_append(prefix, unsigned(specs.upper ? 'X' : 'x') << 8 | '0'); int num_digits = count_digits<4>(abs_value); - return write_int( + return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { - return format_uint<4, Char>(it, abs_value, num_digits, upper); + return format_uint<4, Char>(it, abs_value, num_digits, specs.upper); }); } - case presentation_type::bin_lower: - case presentation_type::bin_upper: { - bool upper = specs.type == presentation_type::bin_upper; - if (specs.alt) - prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0'); - int num_digits = count_digits<1>(abs_value); - return write_int(out, num_digits, prefix, specs, - [=](reserve_iterator it) { - return format_uint<1, Char>(it, abs_value, num_digits); - }); - } case presentation_type::oct: { int num_digits = count_digits<3>(abs_value); // Octal prefix '0' is counted as a digit, so only add it if precision // is not greater than the number of digits. if (specs.alt && specs.precision <= num_digits && abs_value != 0) prefix_append(prefix, '0'); - return write_int(out, num_digits, prefix, specs, - [=](reserve_iterator it) { - return format_uint<3, Char>(it, abs_value, num_digits); - }); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator it) { + return format_uint<3, Char>(it, abs_value, num_digits); + }); + } + case presentation_type::bin: { + if (specs.alt) + prefix_append(prefix, unsigned(specs.upper ? 'B' : 'b') << 8 | '0'); + int num_digits = count_digits<1>(abs_value); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator it) { + return format_uint<1, Char>(it, abs_value, num_digits); + }); } case presentation_type::chr: - return write_char(out, static_cast(abs_value), specs); - default: - throw_format_error("invalid type specifier"); + return write_char(out, static_cast(abs_value), specs); } - return out; } template -FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline( - OutputIt out, write_int_arg arg, const basic_format_specs& specs, - locale_ref loc) -> OutputIt { - return write_int(out, arg, specs, loc); +FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(OutputIt out, + write_int_arg arg, + const format_specs& specs, + locale_ref loc) -> OutputIt { + return write_int(out, arg, specs, loc); } -template ::value && !std::is_same::value && - std::is_same>::value)> -FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const basic_format_specs& specs, - locale_ref loc) -> OutputIt { - return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs, - loc); + !std::is_same::value)> +FMT_CONSTEXPR FMT_INLINE auto write(basic_appender out, T value, + const format_specs& specs, locale_ref loc) + -> basic_appender { + if (specs.localized && write_loc(out, value, specs, loc)) return out; + return write_int_noinline(out, make_write_int_arg(value, specs.sign), + specs, loc); } // An inlined version of write used in format string compilation. template ::value && !std::is_same::value && - !std::is_same>::value)> + !std::is_same::value && + !std::is_same>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const basic_format_specs& specs, - locale_ref loc) -> OutputIt { - return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); + const format_specs& specs, locale_ref loc) + -> OutputIt { + if (specs.localized && write_loc(out, value, specs, loc)) return out; + return write_int(out, make_write_int_arg(value, specs.sign), specs, + loc); } +// An output iterator that counts the number of objects written to it and +// discards them. +class counting_iterator { + private: + size_t count_; + + public: + using iterator_category = std::output_iterator_tag; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = void; + FMT_UNCHECKED_ITERATOR(counting_iterator); + + struct value_type { + template FMT_CONSTEXPR void operator=(const T&) {} + }; + + FMT_CONSTEXPR counting_iterator() : count_(0) {} + + FMT_CONSTEXPR auto count() const -> size_t { return count_; } + + FMT_CONSTEXPR auto operator++() -> counting_iterator& { + ++count_; + return *this; + } + FMT_CONSTEXPR auto operator++(int) -> counting_iterator { + auto it = *this; + ++*this; + return it; + } + + FMT_CONSTEXPR friend auto operator+(counting_iterator it, difference_type n) + -> counting_iterator { + it.count_ += static_cast(n); + return it; + } + + FMT_CONSTEXPR auto operator*() const -> value_type { return {}; } +}; + template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, - const basic_format_specs& specs) -> OutputIt { + const format_specs& specs) -> OutputIt { auto data = s.data(); auto size = s.size(); if (specs.precision >= 0 && to_unsigned(specs.precision) < size) size = code_point_index(s, to_unsigned(specs.precision)); - auto width = - specs.width != 0 ? compute_width(basic_string_view(data, size)) : 0; - return write_padded(out, specs, size, width, - [=](reserve_iterator it) { - return copy_str(data, data + size, it); - }); + bool is_debug = specs.type == presentation_type::debug; + size_t width = 0; + + if (is_debug) size = write_escaped_string(counting_iterator{}, s).count(); + + if (specs.width != 0) { + if (is_debug) + width = size; + else + width = compute_width(basic_string_view(data, size)); + } + return write_padded(out, specs, size, width, + [=](reserve_iterator it) { + if (is_debug) return write_escaped_string(it, s); + return copy(data, data + size, it); + }); } template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view> s, - const basic_format_specs& specs, locale_ref) - -> OutputIt { - check_string_type_spec(specs.type); - return write(out, s, specs); + const format_specs& specs, locale_ref) -> OutputIt { + return write(out, s, specs); } template -FMT_CONSTEXPR auto write(OutputIt out, const Char* s, - const basic_format_specs& specs, locale_ref) - -> OutputIt { - return check_cstring_type_spec(specs.type) - ? write(out, basic_string_view(s), specs, {}) - : write_ptr(out, to_uintptr(s), &specs); +FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs& specs, + locale_ref) -> OutputIt { + if (specs.type == presentation_type::pointer) + return write_ptr(out, bit_cast(s), &specs); + if (!s) report_error("string pointer is null"); + return write(out, basic_string_view(s), specs, {}); +} + +template ::value && + !std::is_same::value && + !std::is_same::value)> +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { + auto abs_value = static_cast>(value); + bool negative = is_negative(value); + // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. + if (negative) abs_value = ~abs_value + 1; + int num_digits = count_digits(abs_value); + auto size = (negative ? 1 : 0) + static_cast(num_digits); + auto it = reserve(out, size); + if (auto ptr = to_pointer(it, size)) { + if (negative) *ptr++ = static_cast('-'); + format_decimal(ptr, abs_value, num_digits); + return out; + } + if (negative) *it++ = static_cast('-'); + it = format_decimal(it, abs_value, num_digits).end; + return base_iterator(out, it); +} + +// DEPRECATED! +template +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + format_specs& specs) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto align = align::none; + auto p = begin + code_point_length(begin); + if (end - p <= 0) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': + align = align::left; + break; + case '>': + align = align::right; + break; + case '^': + align = align::center; + break; + } + if (align != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '}') return begin; + if (c == '{') { + report_error("invalid fill character '{'"); + return begin; + } + specs.fill = basic_string_view(begin, to_unsigned(p - begin)); + begin = p + 1; + } else { + ++begin; + } + break; + } else if (p == begin) { + break; + } + p = begin; + } + specs.align = align; + return begin; +} + +// A floating-point presentation format. +enum class float_format : unsigned char { + general, // General: exponent notation or fixed point based on magnitude. + exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. + fixed // Fixed point with the default precision of 6, e.g. 0.0012. +}; + +struct float_specs { + int precision; + float_format format : 8; + sign_t sign : 8; + bool locale : 1; + bool binary32 : 1; + bool showpoint : 1; +}; + +// DEPRECATED! +FMT_CONSTEXPR inline auto parse_float_type_spec(const format_specs& specs) + -> float_specs { + auto result = float_specs(); + result.showpoint = specs.alt; + result.locale = specs.localized; + switch (specs.type) { + default: + FMT_FALLTHROUGH; + case presentation_type::none: + result.format = float_format::general; + break; + case presentation_type::exp: + result.format = float_format::exp; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::fixed: + result.format = float_format::fixed; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::general: + result.format = float_format::general; + break; + } + return result; } template -FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isinf, - basic_format_specs specs, - const float_specs& fspecs) -> OutputIt { +FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, + format_specs specs, sign_t sign) + -> OutputIt { auto str = - isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); + isnan ? (specs.upper ? "NAN" : "nan") : (specs.upper ? "INF" : "inf"); constexpr size_t str_size = 3; - auto sign = fspecs.sign; auto size = str_size + (sign ? 1 : 0); // Replace '0'-padding with space for non-finite values. const bool is_zero_fill = - specs.fill.size() == 1 && *specs.fill.data() == static_cast('0'); - if (is_zero_fill) specs.fill[0] = static_cast(' '); - return write_padded(out, specs, size, [=](reserve_iterator it) { - if (sign) *it++ = detail::sign(sign); - return copy_str(str, str + str_size, it); - }); + specs.fill.size() == 1 && specs.fill.template get() == '0'; + if (is_zero_fill) specs.fill = ' '; + return write_padded(out, specs, size, + [=](reserve_iterator it) { + if (sign) *it++ = detail::sign(sign); + return copy(str, str + str_size, it); + }); } // A decimal floating-point number significand * pow(10, exp). @@ -1683,18 +2443,18 @@ struct big_decimal_fp { int exponent; }; -constexpr auto get_significand_size(const big_decimal_fp& fp) -> int { - return fp.significand_size; +constexpr auto get_significand_size(const big_decimal_fp& f) -> int { + return f.significand_size; } template -inline auto get_significand_size(const dragonbox::decimal_fp& fp) -> int { - return count_digits(fp.significand); +inline auto get_significand_size(const dragonbox::decimal_fp& f) -> int { + return count_digits(f.significand); } template constexpr auto write_significand(OutputIt out, const char* significand, int significand_size) -> OutputIt { - return copy_str(significand, significand + significand_size, out); + return copy(significand, significand + significand_size, out); } template inline auto write_significand(OutputIt out, UInt significand, @@ -1705,7 +2465,7 @@ template FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int exponent, const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { + if (!grouping.has_separator()) { out = write_significand(out, significand, significand_size); return detail::fill_n(out, exponent, static_cast('0')); } @@ -1721,14 +2481,20 @@ inline auto write_significand(Char* out, UInt significand, int significand_size, int integral_size, Char decimal_point) -> Char* { if (!decimal_point) return format_decimal(out, significand, significand_size).end; - auto end = format_decimal(out + 1, significand, significand_size).end; - if (integral_size == 1) { - out[0] = out[1]; - } else { - std::uninitialized_copy_n(out + 1, integral_size, - make_checked(out, to_unsigned(integral_size))); + out += significand_size + 1; + Char* end = out; + int floating_size = significand_size - integral_size; + for (int i = floating_size / 2; i > 0; --i) { + out -= 2; + copy2(out, digits2(static_cast(significand % 100))); + significand /= 100; + } + if (floating_size % 2 != 0) { + *--out = static_cast('0' + significand % 10); + significand /= 10; } - out[integral_size] = decimal_point; + *--out = decimal_point; + format_decimal(out - integral_size, significand, integral_size); return end; } @@ -1741,19 +2507,19 @@ inline auto write_significand(OutputIt out, UInt significand, Char buffer[digits10() + 2]; auto end = write_significand(buffer, significand, significand_size, integral_size, decimal_point); - return detail::copy_str_noinline(buffer, end, out); + return detail::copy_noinline(buffer, end, out); } template FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, int significand_size, int integral_size, Char decimal_point) -> OutputIt { - out = detail::copy_str_noinline(significand, - significand + integral_size, out); + out = detail::copy_noinline(significand, significand + integral_size, + out); if (!decimal_point) return out; *out++ = decimal_point; - return detail::copy_str_noinline(significand + integral_size, - significand + significand_size, out); + return detail::copy_noinline(significand + integral_size, + significand + significand_size, out); } template @@ -1761,28 +2527,28 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int integral_size, Char decimal_point, const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { + if (!grouping.has_separator()) { return write_significand(out, significand, significand_size, integral_size, decimal_point); } auto buffer = basic_memory_buffer(); - write_significand(buffer_appender(buffer), significand, - significand_size, integral_size, decimal_point); + write_significand(basic_appender(buffer), significand, significand_size, + integral_size, decimal_point); grouping.apply( out, basic_string_view(buffer.data(), to_unsigned(integral_size))); - return detail::copy_str_noinline(buffer.data() + integral_size, - buffer.end(), out); + return detail::copy_noinline(buffer.data() + integral_size, + buffer.end(), out); } -template > -FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp, - const basic_format_specs& specs, +FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, + const format_specs& specs, float_specs fspecs, locale_ref loc) -> OutputIt { - auto significand = fp.significand; - int significand_size = get_significand_size(fp); - constexpr Char zero = static_cast('0'); + auto significand = f.significand; + int significand_size = get_significand_size(f); + const Char zero = static_cast('0'); auto sign = fspecs.sign; size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); using iterator = reserve_iterator; @@ -1790,7 +2556,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp, Char decimal_point = fspecs.locale ? detail::decimal_point(loc) : static_cast('.'); - int output_exp = fp.exponent + significand_size - 1; + int output_exp = f.exponent + significand_size - 1; auto use_exp_format = [=]() { if (fspecs.format == float_format::exp) return true; if (fspecs.format != float_format::general) return false; @@ -1814,7 +2580,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp, if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); - char exp_char = fspecs.upper ? 'E' : 'e'; + char exp_char = specs.upper ? 'E' : 'e'; auto write = [=](iterator it) { if (sign) *it++ = detail::sign(sign); // Insert a decimal point after the first digit and add an exponent. @@ -1824,29 +2590,28 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp, *it++ = static_cast(exp_char); return write_exponent(output_exp, it); }; - return specs.width > 0 ? write_padded(out, specs, size, write) - : base_iterator(out, write(reserve(out, size))); + return specs.width > 0 + ? write_padded(out, specs, size, write) + : base_iterator(out, write(reserve(out, size))); } - int exp = fp.exponent + significand_size; - if (fp.exponent >= 0) { + int exp = f.exponent + significand_size; + if (f.exponent >= 0) { // 1234e5 -> 123400000[.0+] - size += to_unsigned(fp.exponent); + size += to_unsigned(f.exponent); int num_zeros = fspecs.precision - exp; -#ifdef FMT_FUZZ - if (num_zeros > 5000) - throw std::runtime_error("fuzz mode - avoiding excessive cpu use"); -#endif + abort_fuzzing_if(num_zeros > 5000); if (fspecs.showpoint) { - if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; - if (num_zeros > 0) size += to_unsigned(num_zeros) + 1; + ++size; + if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 0; + if (num_zeros > 0) size += to_unsigned(num_zeros); } auto grouping = Grouping(loc, fspecs.locale); - size += to_unsigned(grouping.count_separators(significand_size)); - return write_padded(out, specs, size, [&](iterator it) { + size += to_unsigned(grouping.count_separators(exp)); + return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = detail::sign(sign); it = write_significand(it, significand, significand_size, - fp.exponent, grouping); + f.exponent, grouping); if (!fspecs.showpoint) return it; *it++ = decimal_point; return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; @@ -1856,8 +2621,8 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp, int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); auto grouping = Grouping(loc, fspecs.locale); - size += to_unsigned(grouping.count_separators(significand_size)); - return write_padded(out, specs, size, [&](iterator it) { + size += to_unsigned(grouping.count_separators(exp)); + return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = detail::sign(sign); it = write_significand(it, significand, significand_size, exp, decimal_point, grouping); @@ -1872,7 +2637,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp, } bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint; size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); - return write_padded(out, specs, size, [&](iterator it) { + return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = detail::sign(sign); *it++ = zero; if (!pointy) return it; @@ -1886,160 +2651,980 @@ template class fallback_digit_grouping { public: constexpr fallback_digit_grouping(locale_ref, bool) {} - constexpr Char separator() const { return Char(); } + constexpr auto has_separator() const -> bool { return false; } - constexpr int count_separators(int) const { return 0; } + constexpr auto count_separators(int) const -> int { return 0; } template - constexpr Out apply(Out out, basic_string_view) const { + constexpr auto apply(Out out, basic_string_view) const -> Out { return out; } }; -template -FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& fp, - const basic_format_specs& specs, - float_specs fspecs, locale_ref loc) - -> OutputIt { +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, + const format_specs& specs, float_specs fspecs, + locale_ref loc) -> OutputIt { if (is_constant_evaluated()) { - return do_write_float>(out, fp, specs, fspecs, + return do_write_float>(out, f, specs, fspecs, loc); } else { - return do_write_float(out, fp, specs, fspecs, loc); + return do_write_float(out, f, specs, fspecs, loc); } } -template ::value)> -FMT_CONSTEXPR20 bool isinf(T value) { +template constexpr auto isnan(T value) -> bool { + return value != value; // std::isnan doesn't support __float128. +} + +template +struct has_isfinite : std::false_type {}; + +template +struct has_isfinite> + : std::true_type {}; + +template ::value&& + has_isfinite::value)> +FMT_CONSTEXPR20 auto isfinite(T value) -> bool { + constexpr T inf = T(std::numeric_limits::infinity()); + if (is_constant_evaluated()) + return !detail::isnan(value) && value < inf && value > -inf; + return std::isfinite(value); +} +template ::value)> +FMT_CONSTEXPR auto isfinite(T value) -> bool { + T inf = T(std::numeric_limits::infinity()); + // std::isfinite doesn't support __float128. + return !detail::isnan(value) && value < inf && value > -inf; +} + +template ::value)> +FMT_INLINE FMT_CONSTEXPR bool signbit(T value) { if (is_constant_evaluated()) { -#if defined(__cpp_if_constexpr) +#ifdef __cpp_if_constexpr if constexpr (std::numeric_limits::is_iec559) { auto bits = detail::bit_cast(static_cast(value)); - constexpr auto significand_bits = - dragonbox::float_info::significand_bits; - return (bits & exponent_mask()) && - !(bits & ((uint64_t(1) << significand_bits) - 1)); + return (bits >> (num_bits() - 1)) != 0; } #endif } - return std::isinf(value); + return std::signbit(static_cast(value)); } -template ::value)> -FMT_CONSTEXPR20 bool isfinite(T value) { - if (is_constant_evaluated()) { -#if defined(__cpp_if_constexpr) - if constexpr (std::numeric_limits::is_iec559) { - auto bits = detail::bit_cast(static_cast(value)); - return (bits & exponent_mask()) != exponent_mask(); +inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { + // Adjust fixed precision by exponent because it is relative to decimal + // point. + if (exp10 > 0 && precision > max_value() - exp10) + FMT_THROW(format_error("number is too big")); + precision += exp10; +} + +class bigint { + private: + // A bigint is stored as an array of bigits (big digits), with bigit at index + // 0 being the least significant one. + using bigit = uint32_t; + using double_bigit = uint64_t; + enum { bigits_capacity = 32 }; + basic_memory_buffer bigits_; + int exp_; + + FMT_CONSTEXPR20 auto operator[](int index) const -> bigit { + return bigits_[to_unsigned(index)]; + } + FMT_CONSTEXPR20 auto operator[](int index) -> bigit& { + return bigits_[to_unsigned(index)]; + } + + static constexpr const int bigit_bits = num_bits(); + + friend struct formatter; + + FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) { + auto result = static_cast((*this)[index]) - other - borrow; + (*this)[index] = static_cast(result); + borrow = static_cast(result >> (bigit_bits * 2 - 1)); + } + + FMT_CONSTEXPR20 void remove_leading_zeros() { + int num_bigits = static_cast(bigits_.size()) - 1; + while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; + bigits_.resize(to_unsigned(num_bigits + 1)); + } + + // Computes *this -= other assuming aligned bigints and *this >= other. + FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) { + FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); + FMT_ASSERT(compare(*this, other) >= 0, ""); + bigit borrow = 0; + int i = other.exp_ - exp_; + for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) + subtract_bigits(i, other.bigits_[j], borrow); + while (borrow > 0) subtract_bigits(i, 0, borrow); + remove_leading_zeros(); + } + + FMT_CONSTEXPR20 void multiply(uint32_t value) { + const double_bigit wide_value = value; + bigit carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + double_bigit result = bigits_[i] * wide_value + carry; + bigits_[i] = static_cast(result); + carry = static_cast(result >> bigit_bits); } -#endif + if (carry != 0) bigits_.push_back(carry); + } + + template ::value || + std::is_same::value)> + FMT_CONSTEXPR20 void multiply(UInt value) { + using half_uint = + conditional_t::value, uint64_t, uint32_t>; + const int shift = num_bits() - bigit_bits; + const UInt lower = static_cast(value); + const UInt upper = value >> num_bits(); + UInt carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + UInt result = lower * bigits_[i] + static_cast(carry); + carry = (upper * bigits_[i] << shift) + (result >> bigit_bits) + + (carry >> bigit_bits); + bigits_[i] = static_cast(result); + } + while (carry != 0) { + bigits_.push_back(static_cast(carry)); + carry >>= bigit_bits; + } + } + + template ::value || + std::is_same::value)> + FMT_CONSTEXPR20 void assign(UInt n) { + size_t num_bigits = 0; + do { + bigits_[num_bigits++] = static_cast(n); + n >>= bigit_bits; + } while (n != 0); + bigits_.resize(num_bigits); + exp_ = 0; + } + + public: + FMT_CONSTEXPR20 bigint() : exp_(0) {} + explicit bigint(uint64_t n) { assign(n); } + + bigint(const bigint&) = delete; + void operator=(const bigint&) = delete; + + FMT_CONSTEXPR20 void assign(const bigint& other) { + auto size = other.bigits_.size(); + bigits_.resize(size); + auto data = other.bigits_.data(); + copy(data, data + size, bigits_.data()); + exp_ = other.exp_; + } + + template FMT_CONSTEXPR20 void operator=(Int n) { + FMT_ASSERT(n > 0, ""); + assign(uint64_or_128_t(n)); + } + + FMT_CONSTEXPR20 auto num_bigits() const -> int { + return static_cast(bigits_.size()) + exp_; + } + + FMT_NOINLINE FMT_CONSTEXPR20 auto operator<<=(int shift) -> bigint& { + FMT_ASSERT(shift >= 0, ""); + exp_ += shift / bigit_bits; + shift %= bigit_bits; + if (shift == 0) return *this; + bigit carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + bigit c = bigits_[i] >> (bigit_bits - shift); + bigits_[i] = (bigits_[i] << shift) + carry; + carry = c; + } + if (carry != 0) bigits_.push_back(carry); + return *this; + } + + template + FMT_CONSTEXPR20 auto operator*=(Int value) -> bigint& { + FMT_ASSERT(value > 0, ""); + multiply(uint32_or_64_or_128_t(value)); + return *this; + } + + friend FMT_CONSTEXPR20 auto compare(const bigint& lhs, const bigint& rhs) + -> int { + int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); + if (num_lhs_bigits != num_rhs_bigits) + return num_lhs_bigits > num_rhs_bigits ? 1 : -1; + int i = static_cast(lhs.bigits_.size()) - 1; + int j = static_cast(rhs.bigits_.size()) - 1; + int end = i - j; + if (end < 0) end = 0; + for (; i >= end; --i, --j) { + bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j]; + if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1; + } + if (i != j) return i > j ? 1 : -1; + return 0; + } + + // Returns compare(lhs1 + lhs2, rhs). + friend FMT_CONSTEXPR20 auto add_compare(const bigint& lhs1, + const bigint& lhs2, const bigint& rhs) + -> int { + auto minimum = [](int a, int b) { return a < b ? a : b; }; + auto maximum = [](int a, int b) { return a > b ? a : b; }; + int max_lhs_bigits = maximum(lhs1.num_bigits(), lhs2.num_bigits()); + int num_rhs_bigits = rhs.num_bigits(); + if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; + if (max_lhs_bigits > num_rhs_bigits) return 1; + auto get_bigit = [](const bigint& n, int i) -> bigit { + return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0; + }; + double_bigit borrow = 0; + int min_exp = minimum(minimum(lhs1.exp_, lhs2.exp_), rhs.exp_); + for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { + double_bigit sum = + static_cast(get_bigit(lhs1, i)) + get_bigit(lhs2, i); + bigit rhs_bigit = get_bigit(rhs, i); + if (sum > rhs_bigit + borrow) return 1; + borrow = rhs_bigit + borrow - sum; + if (borrow > 1) return -1; + borrow <<= bigit_bits; + } + return borrow != 0 ? -1 : 0; + } + + // Assigns pow(10, exp) to this bigint. + FMT_CONSTEXPR20 void assign_pow10(int exp) { + FMT_ASSERT(exp >= 0, ""); + if (exp == 0) return *this = 1; + // Find the top bit. + int bitmask = 1; + while (exp >= bitmask) bitmask <<= 1; + bitmask >>= 1; + // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by + // repeated squaring and multiplication. + *this = 5; + bitmask >>= 1; + while (bitmask != 0) { + square(); + if ((exp & bitmask) != 0) *this *= 5; + bitmask >>= 1; + } + *this <<= exp; // Multiply by pow(2, exp) by shifting. + } + + FMT_CONSTEXPR20 void square() { + int num_bigits = static_cast(bigits_.size()); + int num_result_bigits = 2 * num_bigits; + basic_memory_buffer n(std::move(bigits_)); + bigits_.resize(to_unsigned(num_result_bigits)); + auto sum = uint128_t(); + for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { + // Compute bigit at position bigit_index of the result by adding + // cross-product terms n[i] * n[j] such that i + j == bigit_index. + for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { + // Most terms are multiplied twice which can be optimized in the future. + sum += static_cast(n[i]) * n[j]; + } + (*this)[bigit_index] = static_cast(sum); + sum >>= num_bits(); // Compute the carry. + } + // Do the same for the top half. + for (int bigit_index = num_bigits; bigit_index < num_result_bigits; + ++bigit_index) { + for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) + sum += static_cast(n[i++]) * n[j--]; + (*this)[bigit_index] = static_cast(sum); + sum >>= num_bits(); + } + remove_leading_zeros(); + exp_ *= 2; + } + + // If this bigint has a bigger exponent than other, adds trailing zero to make + // exponents equal. This simplifies some operations such as subtraction. + FMT_CONSTEXPR20 void align(const bigint& other) { + int exp_difference = exp_ - other.exp_; + if (exp_difference <= 0) return; + int num_bigits = static_cast(bigits_.size()); + bigits_.resize(to_unsigned(num_bigits + exp_difference)); + for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) + bigits_[j] = bigits_[i]; + memset(bigits_.data(), 0, to_unsigned(exp_difference) * sizeof(bigit)); + exp_ -= exp_difference; + } + + // Divides this bignum by divisor, assigning the remainder to this and + // returning the quotient. + FMT_CONSTEXPR20 auto divmod_assign(const bigint& divisor) -> int { + FMT_ASSERT(this != &divisor, ""); + if (compare(*this, divisor) < 0) return 0; + FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); + align(divisor); + int quotient = 0; + do { + subtract_aligned(divisor); + ++quotient; + } while (compare(*this, divisor) >= 0); + return quotient; + } +}; + +// format_dragon flags. +enum dragon { + predecessor_closer = 1, + fixup = 2, // Run fixup to correct exp10 which can be off by one. + fixed = 4, +}; + +// Formats a floating-point number using a variation of the Fixed-Precision +// Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White: +// https://fmt.dev/papers/p372-steele.pdf. +FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, + unsigned flags, int num_digits, + buffer& buf, int& exp10) { + bigint numerator; // 2 * R in (FPP)^2. + bigint denominator; // 2 * S in (FPP)^2. + // lower and upper are differences between value and corresponding boundaries. + bigint lower; // (M^- in (FPP)^2). + bigint upper_store; // upper's value if different from lower. + bigint* upper = nullptr; // (M^+ in (FPP)^2). + // Shift numerator and denominator by an extra bit or two (if lower boundary + // is closer) to make lower and upper integers. This eliminates multiplication + // by 2 during later computations. + bool is_predecessor_closer = (flags & dragon::predecessor_closer) != 0; + int shift = is_predecessor_closer ? 2 : 1; + if (value.e >= 0) { + numerator = value.f; + numerator <<= value.e + shift; + lower = 1; + lower <<= value.e; + if (is_predecessor_closer) { + upper_store = 1; + upper_store <<= value.e + 1; + upper = &upper_store; + } + denominator.assign_pow10(exp10); + denominator <<= shift; + } else if (exp10 < 0) { + numerator.assign_pow10(-exp10); + lower.assign(numerator); + if (is_predecessor_closer) { + upper_store.assign(numerator); + upper_store <<= 1; + upper = &upper_store; + } + numerator *= value.f; + numerator <<= shift; + denominator = 1; + denominator <<= shift - value.e; + } else { + numerator = value.f; + numerator <<= shift; + denominator.assign_pow10(exp10); + denominator <<= shift - value.e; + lower = 1; + if (is_predecessor_closer) { + upper_store = 1ULL << 1; + upper = &upper_store; + } + } + int even = static_cast((value.f & 1) == 0); + if (!upper) upper = &lower; + bool shortest = num_digits < 0; + if ((flags & dragon::fixup) != 0) { + if (add_compare(numerator, *upper, denominator) + even <= 0) { + --exp10; + numerator *= 10; + if (num_digits < 0) { + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + if ((flags & dragon::fixed) != 0) adjust_precision(num_digits, exp10 + 1); + } + // Invariant: value == (numerator / denominator) * pow(10, exp10). + if (shortest) { + // Generate the shortest representation. + num_digits = 0; + char* data = buf.data(); + for (;;) { + int digit = numerator.divmod_assign(denominator); + bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. + // numerator + upper >[=] pow10: + bool high = add_compare(numerator, *upper, denominator) + even > 0; + data[num_digits++] = static_cast('0' + digit); + if (low || high) { + if (!low) { + ++data[num_digits - 1]; + } else if (high) { + int result = add_compare(numerator, numerator, denominator); + // Round half to even. + if (result > 0 || (result == 0 && (digit % 2) != 0)) + ++data[num_digits - 1]; + } + buf.try_resize(to_unsigned(num_digits)); + exp10 -= num_digits - 1; + return; + } + numerator *= 10; + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + // Generate the given number of digits. + exp10 -= num_digits - 1; + if (num_digits <= 0) { + auto digit = '0'; + if (num_digits == 0) { + denominator *= 10; + digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; + } + buf.push_back(digit); + return; + } + buf.try_resize(to_unsigned(num_digits)); + for (int i = 0; i < num_digits - 1; ++i) { + int digit = numerator.divmod_assign(denominator); + buf[i] = static_cast('0' + digit); + numerator *= 10; + } + int digit = numerator.divmod_assign(denominator); + auto result = add_compare(numerator, numerator, denominator); + if (result > 0 || (result == 0 && (digit % 2) != 0)) { + if (digit == 9) { + const auto overflow = '0' + 10; + buf[num_digits - 1] = overflow; + // Propagate the carry. + for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] == overflow) { + buf[0] = '1'; + if ((flags & dragon::fixed) != 0) + buf.push_back('0'); + else + ++exp10; + } + return; + } + ++digit; + } + buf[num_digits - 1] = static_cast('0' + digit); +} + +// Formats a floating-point number using the hexfloat format. +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, + buffer& buf) { + // float is passed as double to reduce the number of instantiations and to + // simplify implementation. + static_assert(!std::is_same::value, ""); + + using info = dragonbox::float_info; + + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename info::carrier_uint; + + constexpr auto num_float_significand_bits = + detail::num_significand_bits(); + + basic_fp f(value); + f.e += num_float_significand_bits; + if (!has_implicit_bit()) --f.e; + + constexpr auto num_fraction_bits = + num_float_significand_bits + (has_implicit_bit() ? 1 : 0); + constexpr auto num_xdigits = (num_fraction_bits + 3) / 4; + + constexpr auto leading_shift = ((num_xdigits - 1) * 4); + const auto leading_mask = carrier_uint(0xF) << leading_shift; + const auto leading_xdigit = + static_cast((f.f & leading_mask) >> leading_shift); + if (leading_xdigit > 1) f.e -= (32 - countl_zero(leading_xdigit) - 1); + + int print_xdigits = num_xdigits - 1; + if (specs.precision >= 0 && print_xdigits > specs.precision) { + const int shift = ((print_xdigits - specs.precision - 1) * 4); + const auto mask = carrier_uint(0xF) << shift; + const auto v = static_cast((f.f & mask) >> shift); + + if (v >= 8) { + const auto inc = carrier_uint(1) << (shift + 4); + f.f += inc; + f.f &= ~(inc - 1); + } + + // Check long double overflow + if (!has_implicit_bit()) { + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + if ((f.f & implicit_bit) == implicit_bit) { + f.f >>= 4; + f.e += 4; + } + } + + print_xdigits = specs.precision; + } + + char xdigits[num_bits() / 4]; + detail::fill_n(xdigits, sizeof(xdigits), '0'); + format_uint<4>(xdigits, f.f, num_xdigits, specs.upper); + + // Remove zero tail + while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits; + + buf.push_back('0'); + buf.push_back(specs.upper ? 'X' : 'x'); + buf.push_back(xdigits[0]); + if (specs.alt || print_xdigits > 0 || print_xdigits < specs.precision) + buf.push_back('.'); + buf.append(xdigits + 1, xdigits + 1 + print_xdigits); + for (; print_xdigits < specs.precision; ++print_xdigits) buf.push_back('0'); + + buf.push_back(specs.upper ? 'P' : 'p'); + + uint32_t abs_e; + if (f.e < 0) { + buf.push_back('-'); + abs_e = static_cast(-f.e); + } else { + buf.push_back('+'); + abs_e = static_cast(f.e); + } + format_decimal(appender(buf), abs_e, detail::count_digits(abs_e)); +} + +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, + buffer& buf) { + format_hexfloat(static_cast(value), specs, buf); +} + +constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { + // For checking rounding thresholds. + // The kth entry is chosen to be the smallest integer such that the + // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. + // It is equal to ceil(2^31 + 2^32/10^(k + 1)). + // These are stored in a string literal because we cannot have static arrays + // in constexpr functions and non-static ones are poorly optimized. + return U"\x9999999a\x828f5c29\x80418938\x80068db9\x8000a7c6\x800010c7" + U"\x800001ae\x8000002b"[index]; +} + +template +FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, + buffer& buf) -> int { + // float is passed as double to reduce the number of instantiations. + static_assert(!std::is_same::value, ""); + FMT_ASSERT(value >= 0, "value is negative"); + auto converted_value = convert_float(value); + + const bool fixed = specs.format == float_format::fixed; + if (value <= 0) { // <= instead of == to silence a warning. + if (precision <= 0 || !fixed) { + buf.push_back('0'); + return 0; + } + buf.try_resize(to_unsigned(precision)); + fill_n(buf.data(), precision, '0'); + return -precision; + } + + int exp = 0; + bool use_dragon = true; + unsigned dragon_flags = 0; + if (!is_fast_float() || is_constant_evaluated()) { + const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10) + using info = dragonbox::float_info; + const auto f = basic_fp(converted_value); + // Compute exp, an approximate power of 10, such that + // 10^(exp - 1) <= value < 10^exp or 10^exp <= value < 10^(exp + 1). + // This is based on log10(value) == log2(value) / log2(10) and approximation + // of log2(value) by e + num_fraction_bits idea from double-conversion. + auto e = (f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10; + exp = static_cast(e); + if (e > exp) ++exp; // Compute ceil. + dragon_flags = dragon::fixup; + } else if (precision < 0) { + // Use Dragonbox for the shortest format. + if (specs.binary32) { + auto dec = dragonbox::to_decimal(static_cast(value)); + write(appender(buf), dec.significand); + return dec.exponent; + } + auto dec = dragonbox::to_decimal(static_cast(value)); + write(appender(buf), dec.significand); + return dec.exponent; + } else { + // Extract significand bits and exponent bits. + using info = dragonbox::float_info; + auto br = bit_cast(static_cast(value)); + + const uint64_t significand_mask = + (static_cast(1) << num_significand_bits()) - 1; + uint64_t significand = (br & significand_mask); + int exponent = static_cast((br & exponent_mask()) >> + num_significand_bits()); + + if (exponent != 0) { // Check if normal. + exponent -= exponent_bias() + num_significand_bits(); + significand |= + (static_cast(1) << num_significand_bits()); + significand <<= 1; + } else { + // Normalize subnormal inputs. + FMT_ASSERT(significand != 0, "zeros should not appear here"); + int shift = countl_zero(significand); + FMT_ASSERT(shift >= num_bits() - num_significand_bits(), + ""); + shift -= (num_bits() - num_significand_bits() - 2); + exponent = (std::numeric_limits::min_exponent - + num_significand_bits()) - + shift; + significand <<= shift; + } + + // Compute the first several nonzero decimal significand digits. + // We call the number we get the first segment. + const int k = info::kappa - dragonbox::floor_log10_pow2(exponent); + exp = -k; + const int beta = exponent + dragonbox::floor_log2_pow10(k); + uint64_t first_segment; + bool has_more_segments; + int digits_in_the_first_segment; + { + const auto r = dragonbox::umul192_upper128( + significand << beta, dragonbox::get_cached_power(k)); + first_segment = r.high(); + has_more_segments = r.low() != 0; + + // The first segment can have 18 ~ 19 digits. + if (first_segment >= 1000000000000000000ULL) { + digits_in_the_first_segment = 19; + } else { + // When it is of 18-digits, we align it to 19-digits by adding a bogus + // zero at the end. + digits_in_the_first_segment = 18; + first_segment *= 10; + } + } + + // Compute the actual number of decimal digits to print. + if (fixed) adjust_precision(precision, exp + digits_in_the_first_segment); + + // Use Dragon4 only when there might be not enough digits in the first + // segment. + if (digits_in_the_first_segment > precision) { + use_dragon = false; + + if (precision <= 0) { + exp += digits_in_the_first_segment; + + if (precision < 0) { + // Nothing to do, since all we have are just leading zeros. + buf.try_resize(0); + } else { + // We may need to round-up. + buf.try_resize(1); + if ((first_segment | static_cast(has_more_segments)) > + 5000000000000000000ULL) { + buf[0] = '1'; + } else { + buf[0] = '0'; + } + } + } // precision <= 0 + else { + exp += digits_in_the_first_segment - precision; + + // When precision > 0, we divide the first segment into three + // subsegments, each with 9, 9, and 0 ~ 1 digits so that each fits + // in 32-bits which usually allows faster calculation than in + // 64-bits. Since some compiler (e.g. MSVC) doesn't know how to optimize + // division-by-constant for large 64-bit divisors, we do it here + // manually. The magic number 7922816251426433760 below is equal to + // ceil(2^(64+32) / 10^10). + const uint32_t first_subsegment = static_cast( + dragonbox::umul128_upper64(first_segment, 7922816251426433760ULL) >> + 32); + const uint64_t second_third_subsegments = + first_segment - first_subsegment * 10000000000ULL; + + uint64_t prod; + uint32_t digits; + bool should_round_up; + int number_of_digits_to_print = precision > 9 ? 9 : precision; + + // Print a 9-digits subsegment, either the first or the second. + auto print_subsegment = [&](uint32_t subsegment, char* buffer) { + int number_of_digits_printed = 0; + + // If we want to print an odd number of digits from the subsegment, + if ((number_of_digits_to_print & 1) != 0) { + // Convert to 64-bit fixed-point fractional form with 1-digit + // integer part. The magic number 720575941 is a good enough + // approximation of 2^(32 + 24) / 10^8; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast(720575941)) >> 24) + 1; + digits = static_cast(prod >> 32); + *buffer = static_cast('0' + digits); + number_of_digits_printed++; + } + // If we want to print an even number of digits from the + // first_subsegment, + else { + // Convert to 64-bit fixed-point fractional form with 2-digits + // integer part. The magic number 450359963 is a good enough + // approximation of 2^(32 + 20) / 10^7; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast(450359963)) >> 20) + 1; + digits = static_cast(prod >> 32); + copy2(buffer, digits2(digits)); + number_of_digits_printed += 2; + } + + // Print all digit pairs. + while (number_of_digits_printed < number_of_digits_to_print) { + prod = static_cast(prod) * static_cast(100); + digits = static_cast(prod >> 32); + copy2(buffer + number_of_digits_printed, digits2(digits)); + number_of_digits_printed += 2; + } + }; + + // Print first subsegment. + print_subsegment(first_subsegment, buf.data()); + + // Perform rounding if the first subsegment is the last subsegment to + // print. + if (precision <= 9) { + // Rounding inside the subsegment. + // We round-up if: + // - either the fractional part is strictly larger than 1/2, or + // - the fractional part is exactly 1/2 and the last digit is odd. + // We rely on the following observations: + // - If fractional_part >= threshold, then the fractional part is + // strictly larger than 1/2. + // - If the MSB of fractional_part is set, then the fractional part + // must be at least 1/2. + // - When the MSB of fractional_part is set, either + // second_third_subsegments being nonzero or has_more_segments + // being true means there are further digits not printed, so the + // fractional part is strictly larger than 1/2. + if (precision < 9) { + uint32_t fractional_part = static_cast(prod); + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (second_third_subsegments != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + // In this case, the fractional part is at least 1/2 if and only if + // second_third_subsegments >= 5000000000ULL, and is strictly larger + // than 1/2 if we further have either second_third_subsegments > + // 5000000000ULL or has_more_segments == true. + else { + should_round_up = second_third_subsegments > 5000000000ULL || + (second_third_subsegments == 5000000000ULL && + ((digits & 1) != 0 || has_more_segments)); + } + } + // Otherwise, print the second subsegment. + else { + // Compilers are not aware of how to leverage the maximum value of + // second_third_subsegments to find out a better magic number which + // allows us to eliminate an additional shift. 1844674407370955162 = + // ceil(2^64/10) < ceil(2^64*(10^9/(10^10 - 1))). + const uint32_t second_subsegment = + static_cast(dragonbox::umul128_upper64( + second_third_subsegments, 1844674407370955162ULL)); + const uint32_t third_subsegment = + static_cast(second_third_subsegments) - + second_subsegment * 10; + + number_of_digits_to_print = precision - 9; + print_subsegment(second_subsegment, buf.data() + 9); + + // Rounding inside the subsegment. + if (precision < 18) { + // The condition third_subsegment != 0 implies that the segment was + // of 19 digits, so in this case the third segment should be + // consisting of a genuine digit from the input. + uint32_t fractional_part = static_cast(prod); + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (third_subsegment != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + else { + // In this case, the segment must be of 19 digits, thus + // the third subsegment should be consisting of a genuine digit from + // the input. + should_round_up = third_subsegment > 5 || + (third_subsegment == 5 && + ((digits & 1) != 0 || has_more_segments)); + } + } + + // Round-up if necessary. + if (should_round_up) { + ++buf[precision - 1]; + for (int i = precision - 1; i > 0 && buf[i] > '9'; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] > '9') { + buf[0] = '1'; + if (fixed) + buf[precision++] = '0'; + else + ++exp; + } + } + buf.try_resize(to_unsigned(precision)); + } + } // if (digits_in_the_first_segment > precision) + else { + // Adjust the exponent for its use in Dragon4. + exp += digits_in_the_first_segment - 1; + } + } + if (use_dragon) { + auto f = basic_fp(); + bool is_predecessor_closer = specs.binary32 + ? f.assign(static_cast(value)) + : f.assign(converted_value); + if (is_predecessor_closer) dragon_flags |= dragon::predecessor_closer; + if (fixed) dragon_flags |= dragon::fixed; + // Limit precision to the maximum possible number of significant digits in + // an IEEE754 double because we don't need to generate zeros. + const int max_double_digits = 767; + if (precision > max_double_digits) precision = max_double_digits; + format_dragon(f, dragon_flags, precision, buf, exp); } - return std::isfinite(value); -} - -template ::value)> -FMT_INLINE FMT_CONSTEXPR bool signbit(T value) { - if (is_constant_evaluated()) { -#ifdef __cpp_if_constexpr - if constexpr (std::numeric_limits::is_iec559) { - auto bits = detail::bit_cast(static_cast(value)); - return (bits & (uint64_t(1) << (num_bits() - 1))) != 0; + if (!fixed && !specs.showpoint) { + // Remove trailing zeros. + auto num_digits = buf.size(); + while (num_digits > 0 && buf[num_digits - 1] == '0') { + --num_digits; + ++exp; } -#endif + buf.try_resize(num_digits); } - return std::signbit(value); + return exp; } -template ::value)> -FMT_CONSTEXPR20 auto write(OutputIt out, T value, - basic_format_specs specs, locale_ref loc = {}) - -> OutputIt { - if (const_check(!is_supported_floating_point(value))) return out; - float_specs fspecs = parse_float_type_spec(specs); - fspecs.sign = specs.sign; +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, + locale_ref loc) -> OutputIt { + sign_t sign = specs.sign; if (detail::signbit(value)) { // value < 0 is false for NaN so use signbit. - fspecs.sign = sign::minus; + sign = sign::minus; value = -value; - } else if (fspecs.sign == sign::minus) { - fspecs.sign = sign::none; + } else if (sign == sign::minus) { + sign = sign::none; } if (!detail::isfinite(value)) - return write_nonfinite(out, detail::isinf(value), specs, fspecs); + return write_nonfinite(out, detail::isnan(value), specs, sign); - if (specs.align == align::numeric && fspecs.sign) { + if (specs.align == align::numeric && sign) { auto it = reserve(out, 1); - *it++ = detail::sign(fspecs.sign); + *it++ = detail::sign(sign); out = base_iterator(out, it); - fspecs.sign = sign::none; + sign = sign::none; if (specs.width != 0) --specs.width; } memory_buffer buffer; - if (fspecs.format == float_format::hex) { - if (fspecs.sign) buffer.push_back(detail::sign(fspecs.sign)); - snprintf_float(promote_float(value), specs.precision, fspecs, buffer); - return write_bytes(out, {buffer.data(), buffer.size()}, - specs); + if (specs.type == presentation_type::hexfloat) { + if (sign) buffer.push_back(detail::sign(sign)); + format_hexfloat(convert_float(value), specs, buffer); + return write_bytes(out, {buffer.data(), buffer.size()}, + specs); } + int precision = specs.precision >= 0 || specs.type == presentation_type::none ? specs.precision : 6; - if (fspecs.format == float_format::exp) { + if (specs.type == presentation_type::exp) { if (precision == max_value()) - throw_format_error("number is too big"); + report_error("number is too big"); else ++precision; + } else if (specs.type != presentation_type::fixed && precision == 0) { + precision = 1; } + float_specs fspecs = parse_float_type_spec(specs); + fspecs.sign = sign; if (const_check(std::is_same())) fspecs.binary32 = true; - fspecs.use_grisu = is_fast_float(); - int exp = format_float(promote_float(value), precision, fspecs, buffer); + int exp = format_float(convert_float(value), precision, fspecs, buffer); fspecs.precision = precision; - auto fp = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; - return write_float(out, fp, specs, fspecs, loc); + auto f = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; + return write_float(out, f, specs, fspecs, loc); +} + +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, + locale_ref loc = {}) -> OutputIt { + if (const_check(!is_supported_floating_point(value))) return out; + return specs.localized && write_loc(out, value, specs, loc) + ? out + : write_float(out, value, specs, loc); } template ::value)> FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { - if (is_constant_evaluated()) { - return write(out, value, basic_format_specs()); - } - + if (is_constant_evaluated()) return write(out, value, format_specs()); if (const_check(!is_supported_floating_point(value))) return out; - using floaty = conditional_t::value, double, T>; - using uint = typename dragonbox::float_info::carrier_uint; - auto bits = bit_cast(value); - - auto fspecs = float_specs(); + auto sign = sign_t::none; if (detail::signbit(value)) { - fspecs.sign = sign::minus; + sign = sign::minus; value = -value; } - constexpr auto specs = basic_format_specs(); - uint mask = exponent_mask(); - if ((bits & mask) == mask) - return write_nonfinite(out, std::isinf(value), specs, fspecs); + constexpr auto specs = format_specs(); + using floaty = conditional_t::value, double, T>; + using floaty_uint = typename dragonbox::float_info::carrier_uint; + floaty_uint mask = exponent_mask(); + if ((bit_cast(value) & mask) == mask) + return write_nonfinite(out, std::isnan(value), specs, sign); + auto fspecs = float_specs(); + fspecs.sign = sign; auto dec = dragonbox::to_decimal(static_cast(value)); - return write_float(out, dec, specs, fspecs, {}); + return write_float(out, dec, specs, fspecs, {}); } template ::value && + FMT_ENABLE_IF(is_floating_point::value && !is_fast_float::value)> inline auto write(OutputIt out, T value) -> OutputIt { - return write(out, value, basic_format_specs()); + return write(out, value, format_specs()); } template -auto write(OutputIt out, monostate, basic_format_specs = {}, - locale_ref = {}) -> OutputIt { +auto write(OutputIt out, monostate, format_specs = {}, locale_ref = {}) + -> OutputIt { FMT_ASSERT(false, ""); return out; } @@ -2048,39 +3633,17 @@ template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) -> OutputIt { auto it = reserve(out, value.size()); - it = copy_str_noinline(value.begin(), value.end(), it); + it = copy_noinline(value.begin(), value.end(), it); return base_iterator(out, it); } template ::value)> + FMT_ENABLE_IF(has_to_string_view::value)> constexpr auto write(OutputIt out, const T& value) -> OutputIt { return write(out, to_string_view(value)); } -template ::value && - !std::is_same::value && - !std::is_same::value)> -FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { - auto abs_value = static_cast>(value); - bool negative = is_negative(value); - // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. - if (negative) abs_value = ~abs_value + 1; - int num_digits = count_digits(abs_value); - auto size = (negative ? 1 : 0) + static_cast(num_digits); - auto it = reserve(out, size); - if (auto ptr = to_pointer(it, size)) { - if (negative) *ptr++ = static_cast('-'); - format_decimal(ptr, abs_value, num_digits); - return out; - } - if (negative) *it++ = static_cast('-'); - it = format_decimal(it, abs_value, num_digits).end; - return base_iterator(out, it); -} - -// FMT_ENABLE_IF() condition separated to workaround MSVC bug +// FMT_ENABLE_IF() condition separated to workaround an MSVC bug. template < typename Char, typename OutputIt, typename T, bool check = @@ -2089,19 +3652,17 @@ template < type::custom_type, FMT_ENABLE_IF(check)> FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { - return write( - out, static_cast::type>(value)); + return write(out, static_cast>(value)); } template ::value)> -FMT_CONSTEXPR auto write(OutputIt out, T value, - const basic_format_specs& specs = {}, +FMT_CONSTEXPR auto write(OutputIt out, T value, const format_specs& specs = {}, locale_ref = {}) -> OutputIt { return specs.type != presentation_type::none && specs.type != presentation_type::string - ? write(out, value ? 1 : 0, specs, {}) - : write_bytes(out, value ? "true" : "false", specs); + ? write(out, value ? 1 : 0, specs, {}) + : write_bytes(out, value ? "true" : "false", specs); } template @@ -2112,46 +3673,50 @@ FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { } template -FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value) - -> OutputIt { - if (!value) { - throw_format_error("string pointer is null"); - } else { - auto length = std::char_traits::length(value); - out = write(out, basic_string_view(value, length)); - } +FMT_CONSTEXPR20 auto write(OutputIt out, const Char* value) -> OutputIt { + if (value) return write(out, basic_string_view(value)); + report_error("string pointer is null"); return out; } template ::value)> -auto write(OutputIt out, const T* value, - const basic_format_specs& specs = {}, locale_ref = {}) - -> OutputIt { - check_pointer_type_spec(specs.type, error_handler()); - return write_ptr(out, to_uintptr(value), &specs); +auto write(OutputIt out, const T* value, const format_specs& specs = {}, + locale_ref = {}) -> OutputIt { + return write_ptr(out, bit_cast(value), &specs); } -template -FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> - typename std::enable_if< - mapped_type_constant>::value == - type::custom_type, - OutputIt>::type { - using context_type = basic_format_context; - using formatter_type = - conditional_t::value, - typename context_type::template formatter_type, - fallback_formatter>; - context_type ctx(out, {}, {}); - return formatter_type().format(value, ctx); +// A write overload that handles implicit conversions. +template > +FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> enable_if_t< + std::is_class::value && !has_to_string_view::value && + !is_floating_point::value && !std::is_same::value && + !std::is_same().map( + value))>>::value, + OutputIt> { + return write(out, arg_mapper().map(value)); +} + +template > +FMT_CONSTEXPR auto write(OutputIt out, const T& value) + -> enable_if_t::value == + type::custom_type && + !std::is_fundamental::value, + OutputIt> { + auto formatter = typename Context::template formatter_type(); + auto parse_ctx = typename Context::parse_context_type({}); + formatter.parse(parse_ctx); + auto ctx = Context(out, {}, {}); + return formatter.format(value, ctx); } // An argument visitor that formats the argument and writes it via the output // iterator. It's a class and not a generic lambda for compatibility with C++11. template struct default_arg_formatter { - using iterator = buffer_appender; - using context = buffer_context; + using iterator = basic_appender; + using context = buffered_context; iterator out; basic_format_args args; @@ -2169,16 +3734,16 @@ template struct default_arg_formatter { }; template struct arg_formatter { - using iterator = buffer_appender; - using context = buffer_context; + using iterator = basic_appender; + using context = buffered_context; iterator out; - const basic_format_specs& specs; + const format_specs& specs; locale_ref locale; template FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator { - return detail::write(out, value, specs, locale); + return detail::write(out, value, specs, locale); } auto operator()(typename basic_format_arg::handle) -> iterator { // User-defined types are handled separately because they require access @@ -2187,122 +3752,49 @@ template struct arg_formatter { } }; -template struct custom_formatter { - basic_format_parse_context& parse_ctx; - buffer_context& ctx; - - void operator()( - typename basic_format_arg>::handle h) const { - h.format(parse_ctx, ctx); - } - template void operator()(T) const {} -}; - -template -using is_integer = - bool_constant::value && !std::is_same::value && - !std::is_same::value && - !std::is_same::value>; - -template class width_checker { - public: - explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} - +struct width_checker { template ::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { - if (is_negative(value)) handler_.on_error("negative width"); + if (is_negative(value)) report_error("negative width"); return static_cast(value); } template ::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { - handler_.on_error("width is not integer"); + report_error("width is not integer"); return 0; } - - private: - ErrorHandler& handler_; }; -template class precision_checker { - public: - explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {} - +struct precision_checker { template ::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { - if (is_negative(value)) handler_.on_error("negative precision"); + if (is_negative(value)) report_error("negative precision"); return static_cast(value); } template ::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { - handler_.on_error("precision is not integer"); + report_error("precision is not integer"); return 0; } - - private: - ErrorHandler& handler_; }; -template