diff --git a/README.md b/README.md
index 0d5b43fa..42db2a4f 100644
--- a/README.md
+++ b/README.md
@@ -3,28 +3,22 @@
[![Build status](https://ci.appveyor.com/api/projects/status/6d1w0d6gw28npxpf?svg=true)](https://ci.appveyor.com/project/HuguesDelorme/mayo)
-[![Build Status](https://img.shields.io/travis/fougue/mayo/develop.svg?logo=travis)](https://travis-ci.org/fougue/mayo)
+[![Build Status](https://img.shields.io/travis/fougue/mayo/develop.svg?logo=travis)](https://app.travis-ci.com/fougue/mayo)
[![License](https://img.shields.io/badge/license-BSD%202--clause-blue.svg)](https://github.com/fougue/mayo/blob/develop/LICENSE.txt)
# What is Mayo
-Mayo is a 3D viewer and converter inspired by FreeCad
+Mayo is an opensource 3D CAD viewer and converter
# Overview
* View and convert 3D files in different formats
-* Explore assembly trees and view properties
+* Explore assembly trees(product structure) and view properties
* Cross platform: runs on Windows, Linux and macOS
* Underlying toolkits: OpenCascade and Qt
# Current features
-* Support of multi-documents, user can open many files in the session
-* Support of STEP/IGES assemblies(product structure and colors)
-* Area and volume properties for meshes and shapes
-* Editable name of STEP/IGES entities
-* Editable 3D properties of the imported items, eg. material, color, display mode, ...
* 3D exploding of the model tree, allowing better exploration of complex designs
* 3D clip planes with configurable capping
* 3D view cube providing intuitive camera manipulation
-* Perspective/orthographic 3D view projection
* Save image(snapshot) of the current 3D view
* Quick access to the CAD files recently open thanks to thumbnails in the Home page
* Toggle visibility of any item from the Model tree(use checkbox)
@@ -46,12 +40,15 @@ Mayo is a 3D viewer and converter inspired by FreeCad
STEP | ✔ | ✔ | AP203, 214, 242(some parts)
IGES | ✔ | ✔ | v5.3
OpenCascade BREP | ✔ | ✔ |
-OBJ | ✔ | ❌ | Requires OpenCascade ≥ v7.4.0
+DXF | ✔ | ❌ |
+OBJ | ✔ | ✔ | Import requires OpenCascade ≥ v7.4.0
Export requires OpenCascade ≥ v7.6.0
glTF | ✔ | ✔ | Import requires OpenCascade ≥ v7.4.0
Export requires OpenCascade ≥ v7.5.0
Supports 1.0, 2.0 and GLB
VRML | ❌ | ✔ | v2.0 UTF8
STL | ✔ | ✔ | ASCII/binary
AMF | ❌ | ✔ | v1.2 Text/ZIP
Requires [gmio](https://github.com/fougue/gmio) ≥ v0.4.0
+Mayo provides precise control over [import](https://github.com/fougue/mayo/wiki/Import-parameters-by-CAD-format) and [export](https://github.com/fougue/mayo/wiki/Export-parameters-by-CAD-format) with many parameters per format.
+
# Gallery
@@ -67,5 +64,5 @@ AMF | ❌ | ✔ | v1.2 Text/ZIP
Requires [gm
# How to build Mayo
-[Build instructions for Windows MSVC](https://github.com/fougue/mayo/wiki/Build-instructions-for-Windows-MSVC)
-[Build instructions for Debian](https://github.com/fougue/mayo/wiki/Build-instructions-for-Debian)
+[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)
diff --git a/appveyor.yml b/appveyor.yml
index 751f3c08..19689da9 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 0.4_build{build}
+version: 0.5_build{build}
image: Visual Studio 2017
platform: x64
@@ -18,10 +18,12 @@ environment:
matrix:
- APPVEYOR_OCC_VERSION: 7.4.0
- APPVEYOR_OCC_VERSION: 7.5.0
+ - APPVEYOR_OCC_VERSION: 7.6.0
cache:
- OpenCASCADE-7.4.0-vc14-64.rar
- OpenCASCADE-7.5.0-vc14-64.rar
+ - OpenCASCADE-7.6.0-vc14-64.rar
install:
- if not exist OpenCASCADE-%APPVEYOR_OCC_VERSION%-vc14-64.rar
diff --git a/i18n/i18n.pro b/i18n/i18n.pro
index 77cb40b9..4209997e 100644
--- a/i18n/i18n.pro
+++ b/i18n/i18n.pro
@@ -1,7 +1,9 @@
HEADERS += \
$$files(../src/base/*.h) \
$$files(../src/io_occ/*.h) \
- $$files(../src/io_gmio/*.h) \
+ $$files(../src/io_dxf/*.h) \
+ $$files(../src/io_gmio/*.h) \
+ $$files(../src/io_image/*.h) \
$$files(../src/graphics/*.h) \
$$files(../src/gui/*.h) \
$$files(../src/app/*.h) \
@@ -10,7 +12,9 @@ HEADERS += \
SOURCES += \
$$files(../src/base/*.cpp) \
$$files(../src/io_occ/*.cpp) \
+ $$files(../src/io_dxf/*.cpp) \
$$files(../src/io_gmio/*.cpp) \
+ $$files(../src/io_image/*.cpp) \
$$files(../src/graphics/*.cpp) \
$$files(../src/gui/*.cpp) \
$$files(../src/app/*.cpp) \
diff --git a/i18n/mayo_en.qm b/i18n/mayo_en.qm
index 9c4a50be..6451f1c6 100644
Binary files a/i18n/mayo_en.qm and b/i18n/mayo_en.qm differ
diff --git a/i18n/mayo_en.ts b/i18n/mayo_en.ts
index 7a9f02d8..1d1c0574 100644
--- a/i18n/mayo_en.ts
+++ b/i18n/mayo_en.ts
@@ -1,207 +1,234 @@
+
+ AppModule
+
+
+ Very Coarse
+
+
+
+ Coarse
+
+
+
+ Normal
+
+
+
+ Precise
+
+
+
+ Very Precise
+
+
+
+ User Defined
+
+
Mayo::AppModule
-
+
English
-
+
French
-
+
System
-
+
Units
-
+
Count Of Decimals
-
+
Schema
-
+
Application
-
+
Language
-
+
Recent Files
-
+
Last Open Folder
-
+
Last Selected Format Filter
-
+
Link With Document Selector
-
+
Graphics
-
+
Show Origin Trihedron By Default
-
+
Clip planes
-
+
Capping
-
+
Capping Hatch
-
+
Mesh Defaults
-
+
Quality
-
+
Chordal Deflection
-
+
Angular Deflection
-
+
Relative
-
+
Instant Zoom Factor
-
+
Color
-
+
Edge Color
-
+
Material
-
+
Show Edges
-
+
Show Nodes
-
+
BRep Meshing
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
Import
-
+
Export
@@ -248,16 +275,6 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho
-
-
-
-
-
-
-
-
-
-
Mayo::DialogAbout
@@ -310,7 +327,7 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho
-
+
@@ -325,47 +342,52 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho
-
+
Shape
-
+
Color
-
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -378,71 +400,71 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
@@ -535,7 +557,7 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho
-
+
@@ -543,22 +565,22 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho
Mayo::GraphicsMeshObjectDriver_ObjectProperties
-
+
Color
-
+
Edge Color
-
+
Show Edges
-
+
Show Nodes
@@ -586,91 +608,210 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho
[Shape] Shaded With Edges
-
+
[Mesh] Wireframe
-
+
[Mesh] Shaded
-
+
[Mesh] Shrink
+
+ Mayo::IO::DxfReader::Properties
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Scaling
+
+
+
+
+ Import annotations
+
+
+
+
+ Group objects by layer
+
+
+
+
+ Font for TEXT objects
+
+
Mayo::IO::GmioAmfWriter::Properties
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+Only applicable if option `{}` is on
-
+
+Only applicable if option `{}` is on
-
+
64bit Float Format
-
+
64bit Float Precision
-
+
Create ZIP Archive
-
+
ZIP Entry Filename
-
+
Use ZIP64 extensions
+
+ Mayo::IO::ImageWriter::Properties
+
+
+ Width
+
+
+
+ Height
+
+
+
+ Background Color
+
+
+
+ Camera Orientation
+
+
+
+ Camera Projection
+
+
+
+ Mayo::IO::ImageWriterI18N
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Width
+
+
+
+
+ Height
+
+
+
+
+ Background Color
+
+
+
+
+ Camera Orientation
+
+
+
+
+ Camera Projection
+
+
+
+
+
+
+
+
+
+
+
+
Mayo::IO::OccBaseMeshReaderProperties
@@ -784,55 +925,120 @@ Only applicable if option `%1` is on
Mayo::IO::OccGltfWriter::Properties
-
+
Coordinates Converter
-
+
Transformation Format
-
+
Format
-
+
Force UV Export
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Node Name Format
+
+
+
+
+ Mesh Name Format
+
+
+
+
+ Embed Textures
+
+
+
+
+ Merge Faces
+
+
+
+
+ Keep 16bit Indices
+
+
+
+
+
+
Mayo::IO::OccIgesReader::Properties
@@ -954,16 +1160,29 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b
Mayo::IO::OccObjReader::Properties
-
+
Single Precision For Vertex Coordinates
-
+
+
+ Mayo::IO::OccObjWriter::Properties
+
+
+
+
+
+
+
+
+ Coordinates Converter
+
+
Mayo::IO::OccStepReader::Properties
@@ -1217,71 +1436,65 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::IO::System
-
-
- %1 is the format identifier and %2 is the file filters string
-
-
-
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
+
@@ -1289,99 +1502,109 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::Main
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
@@ -1476,7 +1699,7 @@ It can be disabled in order to minimize the size of the resulting file.
-
+
Import
@@ -1507,7 +1730,7 @@ It can be disabled in order to minimize the size of the resulting file.
-
+
@@ -1664,95 +1887,101 @@ It can be disabled in order to minimize the size of the resulting file.
-
+
+
+ %1 is the format identifier and %2 is the file filters string
+
+
+
+
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1760,22 +1989,22 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::Mesh_DocumentTreeNodeProperties
-
+
Count Of Nodes
-
+
Count Of Triangles
-
+
Area
-
+
Volume
@@ -1783,7 +2012,7 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::PropertyEditorI18N
-
+
@@ -1791,98 +2020,90 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::PropertyItemDelegate
-
+
-
+
-
+
-
+
-
-
+
+
-
+
- Mayo::Settings
+ Mayo::QStringUtils
-
-
-
-
-
-
- Mayo::StringUtils
-
-
-
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
+
@@ -1962,7 +2183,7 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::WidgetFileSystem
-
+
@@ -1972,57 +2193,57 @@ Last modified: %3
Mayo::WidgetGuiDocument
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2031,52 +2252,52 @@ Last modified: %3
Mayo::WidgetHomeFiles
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
@@ -2109,13 +2330,13 @@ Read: %5
Mayo::WidgetModelTreeBuilder_Xde
-
+
Name Format Of Assembly Instances
-
-
+
+
@@ -2208,99 +2429,119 @@ Read: %5
Mayo::XCaf_DocumentTreeNodeProperties
-
+
Name
-
+
Shape
-
+
XDE Shape Type
-
+
+
+ Layer
+
+
+
Color
-
+
Location
-
+
Centroid
-
+
Area
-
+
Volume
-
+
+
+ Material Density
+
+
+
+
+ Material Name
+
+
+
Product Name
-
+
Product Color
-
+
Product Centroid
-
+
Product Area
-
+
Product Volume
-
- Assembly
+ Assembly
-
- Instance
+ Instance
-
- Component
+ Component
-
- Compound
+ Compound
-
- Simple
+ Simple
-
- Sub
+ Sub
+
+
+
+ OccStlWriter::Properties
+
+
+ Text
+
+
+
+ Binary
diff --git a/i18n/mayo_fr.qm b/i18n/mayo_fr.qm
index d4ae23b6..ef59ab7e 100644
Binary files a/i18n/mayo_fr.qm and b/i18n/mayo_fr.qm differ
diff --git a/i18n/mayo_fr.ts b/i18n/mayo_fr.ts
index 81ba9bcc..a3dad4f1 100644
--- a/i18n/mayo_fr.ts
+++ b/i18n/mayo_fr.ts
@@ -1,180 +1,207 @@
+
+ AppModule
+
+
+ Très grossière
+
+
+
+ Grossière
+
+
+
+ Normale
+
+
+
+ Précise
+
+
+
+ Très précise
+
+
+
+ Custom
+
+
Mayo::AppModule
-
+
Anglais
-
+
Français
-
+
Système
-
+
Unités
-
+
Nombre de décimales
-
+
Schéma
-
+
Application
-
+
Langue
-
+
Fichiers récents
-
+
Dernier répertoire ouvert
-
+
Dernier filtre de format sélectionné
-
+
Lier au sélecteur de documents
-
+
Graphisme
-
+
Afficher le trihèdre Origine par défaut
-
+
Plans de coupe
-
+
Bouchage
-
+
Bouchages avec hachures
-
+
Maillage par défauts
-
+
Qualité
-
+
Déflection chordale
-
+
Déflection angulaire
-
+
Relatif
-
+
Coefficient du zoom instantané
-
+
Couleur
-
+
Couleur des arêtes
-
+
Matériau
-
+
Afficher les arêtes
-
+
Afficher les nœuds
-
+
Maillage BRep
-
+
Langage de l'application. Tout changement sera effectif après redémarrage de l'application
-
+
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
-
+
Pour la tesselation des faces, la déflection angulaire limite l'angle entre les segments successifs d'une polyligne
-
+
Contrôle la précision du maillage calculé à partir de la forme BRep
-
+
Pour la tesselation des faces, la déflection chordale limite la distance entre une courbe et sa discrétisation
-
+
@@ -183,27 +210,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` × `TailleArête`. La déflection utilisée pour les faces sera la déflection maximale de ses arêtes.
-
+
Montrer/cacher par défaut le trièdre positionné à l'orgine "monde". N'affecte pas la vue 3D des documents actuellement ouverts
-
+
Activer le bouchage des graphismes actuellement coupés
-
+
Activer le hachage texturé pour le bouchage des graphismes actuellement coupés
-
+
Import
-
+
Export
@@ -251,14 +278,8 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera
-
-
-
-
-
-
-
+ '%1' ne dispose pas des permissions de lecture
@@ -312,7 +333,7 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera
XDE
-
+
@@ -327,47 +348,52 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera
Non
-
+
Forme
-
+
Couleur
-
+
+
+
+
+
+
-
+
-
+
-
+
-
+
Erreur
-
+
Ce document n'est pas XDE-compatible
-
+
Attributs
@@ -380,71 +406,71 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera
Options
-
+
Restaurer les valeurs par défaut
-
+
%1 / %2
-
+
Échanger
-
+
Charger le fichier ...
-
+
Sauvergarder vers ...
-
-
+
+
Choisir fichier INI
-
-
+
+
Fichiers INI(*.ini)
-
-
-
+
+
+
Erreur
-
+
'%1' n'existe pas
-
+
'%1' ne dispose pas des permissions de lecture
-
+
Erreur lors de l'écriture vers '%1'
-
+
Restaurer les valeurs seulement pour la section par défaut
-
+
Restaure les valeurs pour tout le groupe
@@ -537,7 +563,7 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera
Tâches
-
+
/
@@ -545,22 +571,22 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera
Mayo::GraphicsMeshObjectDriver_ObjectProperties
-
+
Couleur
-
+
Couleur des arêtes
-
+
Montrer les arêtes
-
+
Montrer les nœuds
@@ -588,93 +614,236 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera
[Forme] Ombré avec arêtes
-
+
[Maillage] Filaire
-
+
[Maillage] Ombré
-
+
[Maillage] Rétréci
+
+ Mayo::IO::DxfReader::Properties
+
+
+
+ Redimensionner les entités selon un facteur d'échelle
+
+
+
+
+ Importer les entités texte et dimension
+
+
+
+
+ Grouper toutes les entités d'une même couche dans une forme compound BREP
+
+
+
+
+ Nom de la police de caractères à utiliser lors de la création des formes correspondantes aux entités TEXT
+
+
+
+
+ Mise à l'échelle
+
+
+
+
+ Import des annotations
+
+
+
+
+ Grouper les entitiés par couche
+
+
+
+
+ Police de caractères pour le texte
+
+
Mayo::IO::GmioAmfWriter::Properties
-
+
Format à utiliser lors de l'écriture des valeurs de type `double`en chaînes de caractères
-
+
Décimal à virgule flottante (ex: 392,65)
-
+
Notation scientifique (ex: 3,9265E+2)
-
+
Utiliser la notation la plus compacte : décimale ou scientifique
-
+
Nombre maximal de chiffres significatifs lors de l'écriture de valeurs de type `double`
-
+
Écrire le document AMF dans une archive ZIP contenant une entrée de fichier
-
+
+Only applicable if option `{}` is on
Nom de l'entrée du fichier AMF dans l'archive ZIP.
+Seulement applicable si l'option `{}` est activée
+
+
+
+
+ Utiliser les extensions de format ZIP64.
+Seulement applicable si l'option `{}` est activée
+
+
+
+ Nom de l'entrée du fichier AMF dans l'archive ZIP.
Seulement applicable si l'option `%1` est activée
-
- Utiliser les extensions de format ZIP64.
+ Utiliser les extensions de format ZIP64.
Seulement applicable si l'option `%1` est activée
-
+
Format nombres flottants 64bit
-
+
Précision nombres flottants 64bit
-
+
Créer une archive ZIP
-
+
Nom du fichier de l'entrée ZIP
-
+
Utiliser les extensions ZIP64
+
+ Mayo::IO::ImageWriter::Properties
+
+
+ Largeur de l'image en pixels
+
+
+
+ Hauteur de l'image en pixels
+
+
+
+ Orientation de la caméra selon la convention Z-up exprimée en tant que vecteur unitaire
+
+
+
+ Largeur
+
+
+
+ Hauteur
+
+
+
+ Couleur de l'arrière plan
+
+
+
+ Orientation de la caméra
+
+
+
+ Projection de la caméra
+
+
+
+ Mayo::IO::ImageWriterI18N
+
+
+
+ Largeur de l'image en pixels
+
+
+
+
+ Hauteur de l'image en pixels
+
+
+
+
+ Orientation de la caméra selon la convention Z-up exprimée en tant que vecteur unitaire
+
+
+
+
+ Largeur
+
+
+
+
+ Hauteur
+
+
+
+
+ Couleur de l'arrière plan
+
+
+
+
+ Orientation de la caméra
+
+
+
+
+ Projection de la caméra
+
+
+
+
+ Aucun élément transféré
+
+
+
+
+ Le vecteur d'orienation de la caméra ne doit pas être nul
+
+
Mayo::IO::OccBaseMeshReaderProperties
@@ -792,55 +961,142 @@ Seulement applicable si l'option `%1` est activée
Mayo::IO::OccGltfWriter::Properties
-
+
Convertisseur de coordonnées
-
+
Format de transformation
-
+
Format
-
+
Forcer l'export UV
-
+
Transformation des coordonnées d'OpenCascade vers glTF
-
+
Transformation préférée pour l'écriture des fichiers glTF
-
+
Exporter les coordonnées UV même si aucune texture mappée
-
+
Choisir automatiquement la représentation la plus compacte entre Mat4 et TRS
-
+
Mactrice de transformation 4x4
-
+
Transformation décomposée en vecteur de translation, quaternion de rotation et facteur d'échelle (T x R x S)
+
+
+
+ Format du nom utilisé pour exporter la hiérarchie de nœuds
+
+
+
+
+ Format du nom utilisé pour exporter la hiérarchie de maillages
+
+
+
+
+ Incorporer les textures (images) dans le fichier cible.
+
+Si l'option est décochée alors les textures sont écrites dans des fichiers séparés.
+
+Applicable seulement si `{1}` est affectée à l'option `{0}`
+
+
+
+ Incorporer les textures (images) dans le fichier cible.
+
+If set to `false` then texture images will be written as separate files.
+
+Applicable only if option `{}` is set to `{}`
+
+
+
+
+ Fusionner les faces au sein d'une même pièce.
+
+Peut réduire la taille JSON grâce à une quantité réduite de tableaux de primitives
+
+
+
+
+ Préférer l'utilisation d'indices 16bit lors de la fusion des faces.
+
+Peut réduite la taille des données grâce à une quantité réduite d'index de triangles.
+
+Applicable seulement si l'option `{}` est cochée
+
+
+
+
+ Format du nom pour les nœuds
+
+
+
+
+ Format du nom pour les maillages
+
+
+
+
+ Incorporer les textures dans le même fichier cible
+
+
+
+
+ Fusionner les faces
+
+
+
+
+ Utiliser des indices 16bit
+
+
+
+
+
+
Mayo::IO::OccIgesReader::Properties
@@ -962,16 +1218,29 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b
Mayo::IO::OccObjReader::Properties
-
+
Coordonnées sommet en précision simple
-
+
+
+ Mayo::IO::OccObjWriter::Properties
+
+
+
+
+
+
+
+
+ Convertisseur de coordonnées
+
+
Mayo::IO::OccStepReader::Properties
@@ -1225,73 +1494,84 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::IO::System
-
%1 is the format identifier and %2 is the file filters string
- %1 fichiers (%2)
+ %1 fichiers (%2)
-
- Erreur pendant l'import de '%1'
+ Erreur pendant l'import de '%1'
%2
-
+
Lecture fichier
-
+
Format inconnu
-
+
+
+ Erreur pendant l'import de '{}'
+{}
+
+
+
Aucun lecteur compatible
-
+
Problème de lecture fichier
-
+
Transfert du fichier
-
-
+
+
Problème de transfert fichier
-
+
+
+ Erreur pendant l'export de '{}'
+{}
+
+
- Erreur pendant l'export de '%1'
+ Erreur pendant l'export de '%1'
%2
-
+
Aucun writer compatible
-
+
Transfert
-
+
Écriture
-
+
Problème d'écriture fichier
@@ -1299,100 +1579,132 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::Main
-
+
Mayo, une visionneuse 3D en code libre basée surQt5/OpenCascade
-
+
Thème de l'IHM (classic|dark)
-
+
nom
-
+
Désactiver l'indicateur de progression dans la sortie console (mode CLI seulement)
-
+
files
-
+
Fichiers à ouvrir au démarrage, optionnel
-
+
[fichiers ...]
-
+
+
+ Le fichier de configuration OpenCascade n'existe pas ou non lisible [chemin=%1]
+
+
+
+
+ Le fichier de configuration OpenCascade n'a pu être chargé par QSettings [chemin=%1]
+
+
+
+
+ Échec chargement du fichier de traductions [chemin=%1]
+
+
+
+
+ Export de {} terminé
+
+
+
+
+ Export de {} en cours ...
+
+
+
+
+
+ Échec chargement du fichier de configuration [chemin=%1]
+
+
+
+
+
+
+
+
Fichier de configuration (format INI) à charger au démarrage
-
-
+
+
-
+
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 ...)
-
+
Maillage des formes BRep
-
+
Importé
-
+
Import en cours ...
-
- Export de %1 terminé
+ Export de %1 terminé
-
- Export de %1 en cours ...
+ Export de %1 en cours ...
-
+
Auncun fichier en entrée -> aucun export
-
+
Impossible de charger le thème '%1'
-
- Impossible de charger le fichier de configuration '%1'
+ Impossible de charger le fichier de configuration '%1'
-
- Impossible de charger la traduction pour '%1'
+ Impossible de charger la traduction pour '%1'
@@ -1486,7 +1798,7 @@ It can be disabled in order to minimize the size of the resulting file.
-
+
Importer
@@ -1517,7 +1829,7 @@ It can be disabled in order to minimize the size of the resulting file.
-
+
@@ -1674,95 +1986,109 @@ It can be disabled in order to minimize the size of the resulting file.
Montrer/cacher les statistiques de rendu
-
+
+
+ %1 is the format identifier and %2 is the file filters string
+ %1 fichiers (%2)
+
+
+
Tous les fichiers (*.*)
-
+
Selectionner fichier pièce
-
+
Avertissement
-
-
+
+
Erreur
-
+
À propos %1
-
+
Anonyme%1
-
-
+
+
Maillage des formes BRep
-
-
+
+
+
+ Durée import: {}ms
+
+
+
+
+ Durée export: {}ms
+
+
- Temps import : %1ms
+ Temps import : %1ms
-
+
Sélection fichier de sortie
-
- Temps export : %1ms
+ Temps export : %1ms
-
+
Données
-
+
Graphismes
-
+
Fermer %1
-
+
Fermer
-
+
Tout fermer sauf %1
-
+
Tout fermer sauf document courant
-
+
-
+
Vider le menu
@@ -1770,22 +2096,22 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::Mesh_DocumentTreeNodeProperties
-
+
Nombre de nœuds
-
+
Nombre de triangles
-
+
Aire
-
+
Volume
@@ -1793,7 +2119,7 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::PropertyEditorI18N
-
+
Choix couleur ...
@@ -1801,102 +2127,125 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::PropertyItemDelegate
-
+
%1j
-
+
%1h
-
+
%1min
-
+
%1s
-
-
+
+
%1%2
-
+
ERREUR aucune transformation en string pour les propriétés de type '%1'
- Mayo::Settings
+ Mayo::QStringUtils
-
-
-
-
-
-
- Mayo::StringUtils
-
-
-
+
+
-
+ (%1 %2 %3)
-
+
-
+ [%1; %2%3; %4]
-
-
-
-
-
+
+
+
+
+
%1%2
-
+
o
-
+
Ko
-
-
-
+
+
+
Mo
-
-
+
+
Oui
-
-
+
+
Non
-
+
Partiellement
+
+ Mayo::StringUtils
+
+
+ %1%2
+
+
+
+ o
+
+
+
+ Ko
+
+
+
+ Mo
+
+
+
+ Oui
+
+
+
+ Non
+
+
+
+ Partiellement
+
+
Mayo::WidgetClipPlanes
@@ -1972,7 +2321,7 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::WidgetFileSystem
-
+
@@ -1986,57 +2335,57 @@ Modifié le: %3 {1
Mayo::WidgetGuiDocument
-
+
Adapter à tout
-
+
Éditer les plans de coupe
-
+
Éclater l'assemblage
-
+
Isométrique
-
+
Arrière
-
+
Devant
-
+
Gauche
-
+
Droit
-
+
Haut
-
+
Bas
-
+
<b>Click gauche</b> : menu déroulant des vues pré-définies
@@ -2046,12 +2395,12 @@ Modifié le: %3 {1
Mayo::WidgetHomeFiles
-
+
Nouveau Document
-
+
@@ -2060,12 +2409,12 @@ Create and add an empty document where you can import files
Créer et ajouter un document vide où importer des fichiers
-
+
Ouvrir des Documents
-
+
@@ -2074,28 +2423,28 @@ Select files to load and open as distinct documents
Selectionnez les fichiers à charger et ouvrir comme documents distincts
-
+
aujourd'hui %1
-
+
hier %1
-
-
+
+
%1 %2
-
+
%1 jours %2
-
+
Mayo::WidgetModelTreeBuilder_Xde
-
+
instanceNameFormat
Format des noms d'instance
-
+
+ Show {}
+ Montrer {}
+
+
Show %1
- Montrer %1
+ Montrer %1
@@ -2165,7 +2518,7 @@ Lu: %5
Form
-
+ Form
@@ -2234,99 +2587,166 @@ Lu: %5
Mayo::XCaf_DocumentTreeNodeProperties
-
+
Name
Nom
-
+
Shape
Forme
-
+
XdeShape
Forme XDE
-
+
+ XdeLayer
+ Couche
+
+
+
Color
Couleur
-
+
Location
Placement
-
+
Centroid
Centroïde
-
+
Area
Aire
-
+
Volume
Volume
-
+
+ MaterialDensity
+ Densité matière
+
+
+
+ MaterialName
+ Nom matière
+
+
+
ProductName
Nom du produit
-
+
ProductColor
Couleur du produit
-
+
ProductCentroid
Centroïde du produit
-
+
ProductArea
Aire du produit
-
+
ProductVolume
Volume du produit
-
Assembly
- Assemblage
+ Assemblage
-
Reference
- Instance
+ Instance
-
Component
- Composant
+ Composant
-
Compound
- Composé
+ Composé
-
Simple
- Simple
+ Simple
-
Sub
- Sub
+ Sub
+
+
+
+ OccCommon
+
+ Undefined
+ Indéfini
+
+
+ posYfwd_posZup
+ +Zup
+
+
+ negZfwd_posYup
+ +Yup
+
+
+ Micrometer
+ Micromètre
+
+
+ Millimeter
+ Millimètre
+
+
+ Centimeter
+ Centimètre
+
+
+ Meter
+ Mètre
+
+
+ Kilometer
+ Kilomètre
+
+
+ Inch
+ Pouce
+
+
+ Foot
+ Pied
+
+
+ Mile
+ Mile
+
+
+
+ OccStlWriter::Properties
+
+ Ascii
+ Texte
+
+
+ Binary
+ Binaire
@@ -2500,4 +2920,19 @@ Lu: %5
+
+ WidgetModelTreeBuilder_Xde
+
+ Instance
+ Instance
+
+
+ Product
+ Produit
+
+
+ Both
+ Les Deux
+
+
diff --git a/mayo.pro b/mayo.pro
index a6638fec..b321bad9 100644
--- a/mayo.pro
+++ b/mayo.pro
@@ -14,6 +14,7 @@ CONFIG(debug, debug|release) {
}
QT += core gui widgets
+DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050F00
message(Qt version $$QT_VERSION)
CONFIG += c++17
@@ -25,20 +26,21 @@ CONFIG(debug, debug|release) {
CONFIG += release_with_debuginfo
}
-release_with_debuginfo:*msvc* {
+release_with_debuginfo:msvc {
# https://docs.microsoft.com/en-us/cpp/build/reference/how-to-debug-a-release-build
QMAKE_CXXFLAGS_RELEASE += /Zi
QMAKE_LFLAGS_RELEASE += /DEBUG /INCREMENTAL:NO /OPT:REF /OPT:ICF
}
-*msvc* {
+msvc {
+ DEFINES += NOMINMAX
QMAKE_CXXFLAGS += /we4150 # Deletion of pointer to incomplete type 'XXXX'; no destructor called
QMAKE_CXXFLAGS += /std:c++17
}
-*g++*|*clang* {
+gcc|clang {
QMAKE_CXXFLAGS += -std=c++17
}
-*clang* {
+clang {
# Silent Clang warnings about instantiation of variable 'Mayo::GenericProperty::TypeName'
QMAKE_CXXFLAGS += -Wno-undefined-var-template
}
@@ -47,11 +49,11 @@ release_with_debuginfo:*msvc* {
LIBS += -lc++fs
}
macx {
- QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14
+ QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.15
+# QMAKE_CXXFLAGS += -mmacosx-version-min=10.15
}
-
-*win* {
- LIBS += -lUser32
+win32 {
+ LIBS += -lOpengl32 -lUser32
}
INCLUDEPATH += \
@@ -61,6 +63,8 @@ INCLUDEPATH += \
HEADERS += \
$$files(src/base/*.h) \
$$files(src/io_occ/*.h) \
+ $$files(src/io_dxf/*.h) \
+ $$files(src/io_image/*.h) \
$$files(src/graphics/*.h) \
$$files(src/gui/*.h) \
$$files(src/app/*.h) \
@@ -68,11 +72,15 @@ HEADERS += \
SOURCES += \
$$files(src/base/*.cpp) \
$$files(src/io_occ/*.cpp) \
+ $$files(src/io_dxf/*.cpp) \
+ $$files(src/io_image/*.cpp) \
$$files(src/graphics/*.cpp) \
$$files(src/gui/*.cpp) \
$$files(src/app/*.cpp) \
+ \
+ src/3rdparty/fmt/src/format.cc \
-win* {
+win32 {
QT += winextras
HEADERS += $$files(src/app/windows/*.h)
SOURCES += $$files(src/app/windows/*.cpp)
@@ -98,7 +106,12 @@ OTHER_FILES += \
# OpenCascade
include(opencascade.pri)
-message(OpenCascade version $$OCC_VERSION_STR)
+!isEmpty(OCC_VERSION_STR) {
+ message(OpenCascade version $$OCC_VERSION_STR)
+} else {
+ warning(OpenCascade version )
+}
+
LIBS += \
-lTKBin \
-lTKBinL \
@@ -145,13 +158,16 @@ minOpenCascadeVersion(7, 4, 0) {
SOURCES -= \
src/io_occ/io_occ_base_mesh.cpp \
src/io_occ/io_occ_gltf_reader.cpp \
- src/io_occ/io_occ_obj.cpp
+ src/io_occ/io_occ_obj_reader.cpp
}
!minOpenCascadeVersion(7, 5, 0) {
SOURCES -= src/io_occ/io_occ_gltf_writer.cpp
}
+!minOpenCascadeVersion(7, 6, 0) {
+ SOURCES -= src/io_occ/io_occ_obj_writer.cpp
+}
# -- VRML support
LIBS += -lTKVRML
@@ -178,7 +194,7 @@ for(binPath, CASCADE_LIST_OPTBIN_DIR) {
# -- Create file "opencascade_dlls.iss" that will contain the required OpenCascade DLL files to be
# -- added in the InnoSetup [Files] section
# -- The list of OpenCascade libraries is retrieved from the LIBS QMake variable
-win* {
+win32 {
for(lib, LIBS) {
findTK = $$find(lib, "-lTK")
!isEmpty(findTK) {
diff --git a/opencascade.pri b/opencascade.pri
index 2074201a..ff2678ab 100644
--- a/opencascade.pri
+++ b/opencascade.pri
@@ -18,7 +18,7 @@ isEmpty(CASCADE_DEFINES):CASCADE_DEFINES = $$(CSF_DEFINES)
INCLUDEPATH += $$CASCADE_INC_DIR
-linux-*:DEFINES += \
+linux:DEFINES += \
HAVE_CONFIG_H \
HAVE_FSTREAM \
HAVE_IOSTREAM \
@@ -27,7 +27,7 @@ linux-*:DEFINES += \
DEFINES += $$split(CASCADE_DEFINES, ;)
DEFINES += OCCT_HANDLE_NOCAST
-linux-*:DEFINES += LIN LININTEL OCC_CONVERT_SIGNALS
+linux:DEFINES += LIN LININTEL OCC_CONVERT_SIGNALS
# Find OCC version
OCC_VERSION_FILE_CONTENTS = $$cat($$CASCADE_INC_DIR/Standard_Version.hxx, lines)
@@ -62,16 +62,20 @@ defineTest(minOpenCascadeVersion) {
isEqual(OCC_VERSION_PATCH, $$patch) {
return(true)
}
+
greaterThan(OCC_VERSION_PATCH, $$patch) {
return(true)
}
}
+
greaterThan(OCC_VERSION_MINOR, $$min) {
return(true)
}
}
+
greaterThan(OCC_VERSION_MAJOR, $$maj) {
return(true)
}
+
return(false)
}
diff --git a/src/3rdparty/commit_fmt.txt b/src/3rdparty/commit_fmt.txt
new file mode 100644
index 00000000..11bb43db
--- /dev/null
+++ b/src/3rdparty/commit_fmt.txt
@@ -0,0 +1 @@
+2742611cad4aee6b1a5638bd1ebf132908f4a3d9
\ No newline at end of file
diff --git a/src/3rdparty/fmt/args.h b/src/3rdparty/fmt/args.h
new file mode 100644
index 00000000..36f62220
--- /dev/null
+++ b/src/3rdparty/fmt/args.h
@@ -0,0 +1,244 @@
+// Formatting library for C++ - dynamic format arguments
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_ARGS_H_
+#define FMT_ARGS_H_
+
+#include // std::reference_wrapper
+#include // std::unique_ptr
+#include
+
+#include "core.h"
+
+FMT_BEGIN_NAMESPACE
+
+namespace detail {
+
+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) {
+ 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;
+ };
+
+ template struct typed_node : node<> {
+ T value;
+
+ template
+ FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
+
+ template
+ FMT_CONSTEXPR typed_node(const basic_string_view& arg)
+ : value(arg.data(), arg.size()) {}
+ };
+
+ std::unique_ptr> head_;
+
+ public:
+ template const T& push(const Arg& arg) {
+ auto new_node = std::unique_ptr>(new typed_node(arg));
+ auto& value = new_node->value;
+ new_node->next = std::move(head_);
+ head_ = std::move(new_node);
+ return value;
+ }
+};
+} // 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
+ */
+template
+class dynamic_format_arg_store
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+ // Workaround a GCC template argument substitution bug.
+ : public basic_format_args
+#endif
+{
+ private:
+ using char_type = typename Context::char_type;
+
+ template struct need_copy {
+ static constexpr detail::type mapped_type =
+ detail::mapped_type_constant::value;
+
+ enum {
+ value = !(detail::is_reference_wrapper::value ||
+ std::is_same>::value ||
+ std::is_same>::value ||
+ (mapped_type != detail::type::cstring_type &&
+ mapped_type != detail::type::string_type &&
+ mapped_type != detail::type::custom_type))
+ };
+ };
+
+ template
+ using stored_type = conditional_t::value &&
+ !has_formatter::value &&
+ !detail::is_reference_wrapper::value,
+ std::basic_string, T>;
+
+ // Storage of basic_format_arg must be contiguous.
+ std::vector> data_;
+ std::vector> named_info_;
+
+ // Storage of arguments not fitting into basic_format_arg must grow
+ // without relocation because items in data_ refer to it.
+ detail::dynamic_arg_list dynamic_args_;
+
+ friend class basic_format_args;
+
+ unsigned long long get_types() const {
+ return detail::is_unpacked_bit | data_.size() |
+ (named_info_.empty()
+ ? 0ULL
+ : static_cast(detail::has_named_args_bit));
+ }
+
+ const basic_format_arg* data() const {
+ return named_info_.empty() ? data_.data() : data_.data() + 1;
+ }
+
+ template void emplace_arg(const T& arg) {
+ data_.emplace_back(detail::make_arg(arg));
+ }
+
+ template
+ void emplace_arg(const detail::named_arg& arg) {
+ if (named_info_.empty()) {
+ constexpr const detail::named_arg_info* zero_ptr{nullptr};
+ data_.insert(data_.begin(), {zero_ptr, 0});
+ }
+ data_.emplace_back(detail::make_arg(detail::unwrap(arg.value)));
+ auto pop_one = [](std::vector>* data) {
+ data->pop_back();
+ };
+ std::unique_ptr>, decltype(pop_one)>
+ guard{&data_, pop_one};
+ named_info_.push_back({arg.name, static_cast(data_.size() - 2u)});
+ data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
+ guard.release();
+ }
+
+ 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
+ */
+ template void push_back(const T& arg) {
+ if (detail::const_check(need_copy::value))
+ emplace_arg(dynamic_args_.push>(arg));
+ else
+ emplace_arg(detail::unwrap(arg));
+ }
+
+ /**
+ \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
+ */
+ template void push_back(std::reference_wrapper arg) {
+ static_assert(
+ need_copy::value,
+ "objects of built-in types and string views are always copied");
+ emplace_arg(arg.get());
+ }
+
+ /**
+ 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 =
+ dynamic_args_.push>(arg.name).c_str();
+ if (detail::const_check(need_copy::value)) {
+ emplace_arg(
+ fmt::arg(arg_name, dynamic_args_.push>(arg.value)));
+ } else {
+ emplace_arg(fmt::arg(arg_name, arg.value));
+ }
+ }
+
+ /** 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
+ */
+ 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");
+ data_.reserve(new_cap);
+ named_info_.reserve(new_cap_named);
+ }
+};
+
+FMT_END_NAMESPACE
+
+#endif // FMT_ARGS_H_
diff --git a/src/3rdparty/fmt/chrono.h b/src/3rdparty/fmt/chrono.h
new file mode 100644
index 00000000..61602f68
--- /dev/null
+++ b/src/3rdparty/fmt/chrono.h
@@ -0,0 +1,1375 @@
+// Formatting library for C++ - chrono support
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_CHRONO_H_
+#define FMT_CHRONO_H_
+
+#include
+#include
+#include
+#include
+#include
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+
+// Enable safe chrono durations, unless explicitly disabled.
+#ifndef FMT_SAFE_DURATION_CAST
+# define FMT_SAFE_DURATION_CAST 1
+#endif
+#if FMT_SAFE_DURATION_CAST
+
+// For conversion between std::chrono::durations without undefined
+// behaviour or erroneous results.
+// This is a stripped down version of duration_cast, for inclusion in fmt.
+// See https://github.com/pauldreik/safe_duration_cast
+//
+// Copyright Paul Dreik 2019
+namespace safe_duration_cast {
+
+template ::value &&
+ std::numeric_limits::is_signed ==
+ std::numeric_limits::is_signed)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+ ec = 0;
+ using F = std::numeric_limits;
+ using T = std::numeric_limits;
+ static_assert(F::is_integer, "From must be integral");
+ static_assert(T::is_integer, "To must be integral");
+
+ // A and B are both signed, or both unsigned.
+ if (F::digits <= T::digits) {
+ // From fits in To without any problem.
+ } else {
+ // From does not always fit in To, resort to a dynamic check.
+ if (from < (T::min)() || from > (T::max)()) {
+ // outside range.
+ ec = 1;
+ return {};
+ }
+ }
+ 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.
+ */
+template ::value &&
+ std::numeric_limits::is_signed !=
+ std::numeric_limits::is_signed)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+ ec = 0;
+ using F = std::numeric_limits;
+ using T = std::numeric_limits;
+ static_assert(F::is_integer, "From must be integral");
+ static_assert(T::is_integer, "To must be integral");
+
+ if (detail::const_check(F::is_signed && !T::is_signed)) {
+ // From may be negative, not allowed!
+ if (fmt::detail::is_negative(from)) {
+ ec = 1;
+ return {};
+ }
+ // From is positive. Can it always fit in To?
+ if (F::digits > T::digits &&
+ from > static_cast(detail::max_value())) {
+ ec = 1;
+ return {};
+ }
+ }
+
+ if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
+ from > static_cast(detail::max_value())) {
+ ec = 1;
+ return {};
+ }
+ return static_cast(from); // Lossless conversion.
+}
+
+template ::value)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+ ec = 0;
+ return from;
+} // function
+
+// clang-format off
+/**
+ * converts From to To if possible, otherwise ec is set.
+ *
+ * input | output
+ * ---------------------------------|---------------
+ * NaN | NaN
+ * Inf | Inf
+ * normal, fits in output | converted (possibly lossy)
+ * normal, does not fit in output | ec is set
+ * subnormal | best effort
+ * -Inf | -Inf
+ */
+// clang-format on
+template ::value)>
+FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
+ ec = 0;
+ using T = std::numeric_limits;
+ static_assert(std::is_floating_point::value, "From must be floating");
+ static_assert(std::is_floating_point::value, "To must be floating");
+
+ // catch the only happy case
+ if (std::isfinite(from)) {
+ if (from >= T::lowest() && from <= (T::max)()) {
+ return static_cast(from);
+ }
+ // not within range.
+ ec = 1;
+ return {};
+ }
+
+ // nan and inf will be preserved
+ return static_cast(from);
+} // function
+
+template ::value)>
+FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
+ ec = 0;
+ static_assert(std::is_floating_point::value, "From must be floating");
+ return from;
+}
+
+/**
+ * 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) {
+ using From = std::chrono::duration;
+ ec = 0;
+ // the basic idea is that we need to convert from count() in the from type
+ // to count() in the To type, by multiplying it with this:
+ struct Factor
+ : std::ratio_divide {};
+
+ static_assert(Factor::num > 0, "num must be positive");
+ static_assert(Factor::den > 0, "den must be positive");
+
+ // the conversion is like this: multiply from.count() with Factor::num
+ // /Factor::den and convert it to To::rep, all this without
+ // overflow/underflow. let's start by finding a suitable type that can hold
+ // both To, From and Factor::num
+ using IntermediateRep =
+ typename std::common_type::type;
+
+ // safe conversion to IntermediateRep
+ IntermediateRep count =
+ lossless_integral_conversion(from.count(), ec);
+ if (ec) return {};
+ // multiply with Factor::num without overflow or underflow
+ if (detail::const_check(Factor::num != 1)) {
+ const auto max1 = detail::max_value() / Factor::num;
+ if (count > max1) {
+ ec = 1;
+ return {};
+ }
+ const auto min1 =
+ (std::numeric_limits::min)() / Factor::num;
+ if (count < min1) {
+ ec = 1;
+ return {};
+ }
+ count *= Factor::num;
+ }
+
+ if (detail::const_check(Factor::den != 1)) count /= Factor::den;
+ auto tocount = lossless_integral_conversion(count, ec);
+ return ec ? To() : To(tocount);
+}
+
+/**
+ * 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) {
+ using From = std::chrono::duration;
+ ec = 0;
+ if (std::isnan(from.count())) {
+ // nan in, gives nan out. easy.
+ return To{std::numeric_limits::quiet_NaN()};
+ }
+ // maybe we should also check if from is denormal, and decide what to do about
+ // it.
+
+ // +-inf should be preserved.
+ if (std::isinf(from.count())) {
+ return To{from.count()};
+ }
+
+ // the basic idea is that we need to convert from count() in the from type
+ // to count() in the To type, by multiplying it with this:
+ struct Factor
+ : std::ratio_divide {};
+
+ static_assert(Factor::num > 0, "num must be positive");
+ static_assert(Factor::den > 0, "den must be positive");
+
+ // the conversion is like this: multiply from.count() with Factor::num
+ // /Factor::den and convert it to To::rep, all this without
+ // overflow/underflow. let's start by finding a suitable type that can hold
+ // both To, From and Factor::num
+ using IntermediateRep =
+ typename std::common_type::type;
+
+ // force conversion of From::rep -> IntermediateRep to be safe,
+ // even if it will never happen be narrowing in this context.
+ IntermediateRep count =
+ safe_float_conversion(from.count(), ec);
+ if (ec) {
+ return {};
+ }
+
+ // multiply with Factor::num without overflow or underflow
+ if (Factor::num != 1) {
+ constexpr auto max1 = detail::max_value() /
+ static_cast(Factor::num);
+ if (count > max1) {
+ ec = 1;
+ return {};
+ }
+ constexpr auto min1 = std::numeric_limits::lowest() /
+ static_cast(Factor::num);
+ if (count < min1) {
+ ec = 1;
+ return {};
+ }
+ count *= static_cast(Factor::num);
+ }
+
+ // this can't go wrong, right? den>0 is checked earlier.
+ if (Factor::den != 1) {
+ using common_t = typename std::common_type::type;
+ count /= static_cast(Factor::den);
+ }
+
+ // convert to the to type, safely
+ using ToRep = typename To::rep;
+
+ const ToRep tocount = safe_float_conversion(count, ec);
+ if (ec) {
+ return {};
+ }
+ return To{tocount};
+}
+} // namespace safe_duration_cast
+#endif
+
+// Prevents expansion of a preceding token as a function-style macro.
+// Usage: f FMT_NOMACRO()
+#define FMT_NOMACRO
+
+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
+
+ using codecvt = std::codecvt;
+#if FMT_CLANG_VERSION
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdeprecated"
+ auto& f = std::use_facet(loc);
+# pragma clang diagnostic pop
+#else
+ 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);
+ 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 {
+ FMT_THROW(format_error("failed to format time"));
+ }
+ }
+ return str;
+}
+
+template
+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);
+}
+} // namespace detail
+
+FMT_MODULE_EXPORT_BEGIN
+
+/**
+ 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) {
+ struct dispatcher {
+ std::time_t time_;
+ std::tm tm_;
+
+ dispatcher(std::time_t t) : time_(t) {}
+
+ bool run() {
+ using namespace fmt::detail;
+ return handle(localtime_r(&time_, &tm_));
+ }
+
+ bool handle(std::tm* tm) { return tm != nullptr; }
+
+ bool handle(detail::null<>) {
+ using namespace fmt::detail;
+ return fallback(localtime_s(&tm_, &time_));
+ }
+
+ bool fallback(int res) { return res == 0; }
+
+#if !FMT_MSC_VER
+ bool fallback(detail::null<>) {
+ using namespace fmt::detail;
+ std::tm* tm = std::localtime(&time_);
+ if (tm) tm_ = *tm;
+ return tm != nullptr;
+ }
+#endif
+ };
+ dispatcher lt(time);
+ // Too big time values may be unsupported.
+ if (!lt.run()) FMT_THROW(format_error("time_t value out of range"));
+ return lt.tm_;
+}
+
+inline std::tm localtime(
+ std::chrono::time_point time_point) {
+ return localtime(std::chrono::system_clock::to_time_t(time_point));
+}
+
+/**
+ 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) {
+ struct dispatcher {
+ std::time_t time_;
+ std::tm tm_;
+
+ dispatcher(std::time_t t) : time_(t) {}
+
+ bool run() {
+ using namespace fmt::detail;
+ return handle(gmtime_r(&time_, &tm_));
+ }
+
+ bool handle(std::tm* tm) { return tm != nullptr; }
+
+ bool handle(detail::null<>) {
+ using namespace fmt::detail;
+ return fallback(gmtime_s(&tm_, &time_));
+ }
+
+ bool fallback(int res) { return res == 0; }
+
+#if !FMT_MSC_VER
+ bool fallback(detail::null<>) {
+ std::tm* tm = std::gmtime(&time_);
+ if (tm) tm_ = *tm;
+ return tm != nullptr;
+ }
+#endif
+ };
+ dispatcher gt(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);
+}
+
+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);
+}
+
+// Writes two-digit numbers a, b and c separated by sep to buf.
+// The method by Pavel Novikov based on
+// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/.
+inline void write_digit2_separated(char* buf, unsigned a, unsigned b,
+ unsigned c, char sep) {
+ unsigned long long digits =
+ a | (b << 24) | (static_cast(c) << 48);
+ // Convert each value to BCD.
+ // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b.
+ // The difference is
+ // y - x = a * 6
+ // a can be found from x:
+ // a = floor(x / 10)
+ // then
+ // y = x + a * 6 = x + floor(x / 10) * 6
+ // floor(x / 10) is (x * 205) >> 11 (needs 16 bits).
+ digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6;
+ // Put low nibbles to high bytes and high nibbles to low bytes.
+ digits = ((digits & 0x00f00000f00000f0) >> 4) |
+ ((digits & 0x000f00000f00000f) << 8);
+ 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());
+ }
+};
+
+FMT_BEGIN_DETAIL_NAMESPACE
+
+template FMT_CONSTEXPR inline const char* get_units() {
+ if (std::is_same::value) return "as";
+ if (std::is_same::value) return "fs";
+ if (std::is_same::value) return "ps";
+ if (std::is_same::value) return "ns";
+ if (std::is_same::value) return "µs";
+ if (std::is_same::value) return "ms";
+ if (std::is_same::value) return "cs";
+ if (std::is_same::value) return "ds";
+ if (std::is_same>::value) return "s";
+ if (std::is_same::value) return "das";
+ if (std::is_same::value) return "hs";
+ if (std::is_same::value) return "ks";
+ if (std::is_same::value) return "Ms";
+ if (std::is_same::value) return "Gs";
+ 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 "h";
+ return nullptr;
+}
+
+enum class numeric_system {
+ standard,
+ // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale.
+ alternative
+};
+
+// 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) {
+ auto ptr = begin;
+ while (ptr != end) {
+ auto c = *ptr;
+ if (c == '}') break;
+ if (c != '%') {
+ ++ptr;
+ continue;
+ }
+ if (begin != ptr) handler.on_text(begin, ptr);
+ ++ptr; // consume '%'
+ if (ptr == end) FMT_THROW(format_error("invalid format"));
+ c = *ptr++;
+ switch (c) {
+ case '%':
+ handler.on_text(ptr - 1, ptr);
+ break;
+ case 'n': {
+ const Char newline[] = {'\n'};
+ handler.on_text(newline, newline + 1);
+ break;
+ }
+ case 't': {
+ const Char tab[] = {'\t'};
+ handler.on_text(tab, tab + 1);
+ break;
+ }
+ // Day of the week:
+ case 'a':
+ handler.on_abbr_weekday();
+ break;
+ case 'A':
+ handler.on_full_weekday();
+ break;
+ case 'w':
+ handler.on_dec0_weekday(numeric_system::standard);
+ break;
+ case 'u':
+ handler.on_dec1_weekday(numeric_system::standard);
+ break;
+ // Month:
+ case 'b':
+ handler.on_abbr_month();
+ break;
+ case 'B':
+ handler.on_full_month();
+ break;
+ // Hour, minute, second:
+ case 'H':
+ handler.on_24_hour(numeric_system::standard);
+ break;
+ case 'I':
+ handler.on_12_hour(numeric_system::standard);
+ break;
+ case 'M':
+ handler.on_minute(numeric_system::standard);
+ break;
+ case 'S':
+ handler.on_second(numeric_system::standard);
+ break;
+ // Other:
+ case 'c':
+ handler.on_datetime(numeric_system::standard);
+ break;
+ case 'x':
+ handler.on_loc_date(numeric_system::standard);
+ break;
+ case 'X':
+ handler.on_loc_time(numeric_system::standard);
+ break;
+ case 'D':
+ handler.on_us_date();
+ break;
+ case 'F':
+ handler.on_iso_date();
+ break;
+ case 'r':
+ handler.on_12_hour_time();
+ break;
+ case 'R':
+ handler.on_24_hour_time();
+ break;
+ case 'T':
+ handler.on_iso_time();
+ break;
+ case 'p':
+ handler.on_am_pm();
+ break;
+ case 'Q':
+ handler.on_duration_value();
+ break;
+ case 'q':
+ handler.on_duration_unit();
+ break;
+ case 'z':
+ handler.on_utc_offset();
+ break;
+ case 'Z':
+ handler.on_tz_name();
+ break;
+ // Alternative representation:
+ case 'E': {
+ if (ptr == end) FMT_THROW(format_error("invalid format"));
+ c = *ptr++;
+ switch (c) {
+ case 'c':
+ handler.on_datetime(numeric_system::alternative);
+ break;
+ case 'x':
+ handler.on_loc_date(numeric_system::alternative);
+ break;
+ case 'X':
+ handler.on_loc_time(numeric_system::alternative);
+ break;
+ default:
+ FMT_THROW(format_error("invalid format"));
+ }
+ break;
+ }
+ case 'O':
+ if (ptr == end) FMT_THROW(format_error("invalid format"));
+ c = *ptr++;
+ switch (c) {
+ case 'w':
+ handler.on_dec0_weekday(numeric_system::alternative);
+ break;
+ case 'u':
+ handler.on_dec1_weekday(numeric_system::alternative);
+ break;
+ case 'H':
+ handler.on_24_hour(numeric_system::alternative);
+ break;
+ case 'I':
+ handler.on_12_hour(numeric_system::alternative);
+ break;
+ case 'M':
+ handler.on_minute(numeric_system::alternative);
+ break;
+ case 'S':
+ handler.on_second(numeric_system::alternative);
+ break;
+ default:
+ FMT_THROW(format_error("invalid format"));
+ }
+ break;
+ default:
+ FMT_THROW(format_error("invalid format"));
+ }
+ begin = ptr;
+ }
+ if (begin != ptr) handler.on_text(begin, ptr);
+ return ptr;
+}
+
+template struct null_chrono_spec_handler {
+ FMT_CONSTEXPR void unsupported() {
+ static_cast(this)->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_24_hour(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_us_date() { unsupported(); }
+ FMT_CONSTEXPR void on_iso_date() { unsupported(); }
+ FMT_CONSTEXPR void on_12_hour_time() { unsupported(); }
+ FMT_CONSTEXPR void on_24_hour_time() { unsupported(); }
+ FMT_CONSTEXPR void on_iso_time() { unsupported(); }
+ 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_tz_name() { unsupported(); }
+};
+
+struct chrono_format_checker : null_chrono_spec_handler {
+ FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
+
+ 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_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() {}
+};
+
+template ::value)>
+inline bool isnan(T) {
+ return false;
+}
+template ::value)>
+inline bool isnan(T value) {
+ return std::isnan(value);
+}
+
+template ::value)>
+inline bool isfinite(T) {
+ return true;
+}
+
+// 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 ::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);
+}
+
+template ::value)>
+inline T mod(T x, int y) {
+ return x % static_cast(y);
+}
+template ::value)>
+inline T mod(T x, int y) {
+ return std::fmod(x, static_cast(y));
+}
+
+// If T is an integral type, maps T to its unsigned counterpart, otherwise
+// leaves it unchanged (unlike std::make_unsigned).
+template ::value>
+struct make_unsigned_or_unchanged {
+ using type = T;
+};
+
+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) {
+ // 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_whole_seconds =
+ fmt_safe_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);
+ return ms;
+#else
+ auto s = std::chrono::duration_cast(d);
+ return std::chrono::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) {
+ return write(out, val);
+}
+
+template ::value)>
+OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
+ auto specs = basic_format_specs();
+ specs.precision = precision;
+ specs.type = precision > 0 ? presentation_type::fixed_lower
+ : presentation_type::general_lower;
+ return write(out, val, specs);
+}
+
+template
+OutputIt copy_unit(string_view unit, OutputIt out, Char) {
+ return std::copy(unit.begin(), unit.end(), out);
+}
+
+template
+OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
+ // 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);
+ return std::copy(u.c_str(), u.c_str() + u.size(), out);
+}
+
+template
+OutputIt format_duration_unit(OutputIt out) {
+ if (const char* unit = get_units())
+ return copy_unit(string_view(unit), out, Char());
+ *out++ = '[';
+ out = write(out, Period::num);
+ if (const_check(Period::den != 1)) {
+ *out++ = '/';
+ out = write(out, Period::den);
+ }
+ *out++ = ']';
+ *out++ = 's';
+ return out;
+}
+
+template
+struct chrono_formatter {
+ FormatContext& context;
+ OutputIt out;
+ int precision;
+ bool localized = false;
+ // rep is unsigned to avoid overflow.
+ using rep =
+ conditional_t::value && sizeof(Rep) < sizeof(int),
+ unsigned, typename make_unsigned_or_unchanged::type>;
+ rep val;
+ using seconds = std::chrono::duration;
+ seconds s;
+ using milliseconds = std::chrono::duration;
+ bool negative;
+
+ using char_type = typename FormatContext::char_type;
+
+ explicit chrono_formatter(FormatContext& ctx, OutputIt o,
+ std::chrono::duration d)
+ : context(ctx),
+ out(o),
+ val(static_cast(d.count())),
+ negative(false) {
+ if (d.count() < 0) {
+ val = 0 - val;
+ negative = true;
+ }
+
+ // 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
+ }
+
+ // returns true if nan or inf, writes to out.
+ bool handle_nan_inf() {
+ if (isfinite(val)) {
+ return false;
+ }
+ if (isnan(val)) {
+ write_nan();
+ return true;
+ }
+ // must be +-inf
+ if (val > 0) {
+ write_pinf();
+ } else {
+ write_ninf();
+ }
+ return true;
+ }
+
+ Rep hour() const { return static_cast(mod((s.count() / 3600), 24)); }
+
+ Rep hour12() const {
+ 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)); }
+
+ std::tm time() const {
+ auto time = std::tm();
+ time.tm_hour = to_nonnegative_int(hour(), 24);
+ time.tm_min = to_nonnegative_int(minute(), 60);
+ time.tm_sec = to_nonnegative_int(second(), 60);
+ return time;
+ }
+
+ void write_sign() {
+ if (negative) {
+ *out++ = '-';
+ negative = false;
+ }
+ }
+
+ void write(Rep value, int width) {
+ 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');
+ out = format_decimal(out, n, num_digits).end;
+ }
+
+ void write_nan() { std::copy_n("nan", 3, out); }
+ 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) {
+ 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);
+ }
+
+ void on_text(const char_type* begin, const char_type* end) {
+ std::copy(begin, end, out);
+ }
+
+ // These are not implemented because durations don't have date information.
+ void on_abbr_weekday() {}
+ void on_full_weekday() {}
+ void on_dec0_weekday(numeric_system) {}
+ void on_dec1_weekday(numeric_system) {}
+ void on_abbr_month() {}
+ void on_full_month() {}
+ void on_datetime(numeric_system) {}
+ void on_loc_date(numeric_system) {}
+ void on_loc_time(numeric_system) {}
+ void on_us_date() {}
+ void on_iso_date() {}
+ void on_utc_offset() {}
+ void on_tz_name() {}
+
+ void on_24_hour(numeric_system ns) {
+ if (handle_nan_inf()) return;
+
+ if (ns == numeric_system::standard) return write(hour(), 2);
+ auto time = tm();
+ time.tm_hour = to_nonnegative_int(hour(), 24);
+ format_localized(time, 'H', 'O');
+ }
+
+ void on_12_hour(numeric_system ns) {
+ if (handle_nan_inf()) return;
+
+ if (ns == numeric_system::standard) return write(hour12(), 2);
+ auto time = tm();
+ time.tm_hour = to_nonnegative_int(hour12(), 12);
+ format_localized(time, 'I', 'O');
+ }
+
+ void on_minute(numeric_system ns) {
+ if (handle_nan_inf()) return;
+
+ if (ns == numeric_system::standard) return write(minute(), 2);
+ auto time = tm();
+ time.tm_min = to_nonnegative_int(minute(), 60);
+ format_localized(time, 'M', 'O');
+ }
+
+ void on_second(numeric_system ns) {
+ 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);
+ }
+ return;
+ }
+ auto time = tm();
+ time.tm_sec = to_nonnegative_int(second(), 60);
+ format_localized(time, 'S', 'O');
+ }
+
+ void on_12_hour_time() {
+ if (handle_nan_inf()) return;
+ format_localized(time(), 'r');
+ }
+
+ void on_24_hour_time() {
+ if (handle_nan_inf()) {
+ *out++ = ':';
+ handle_nan_inf();
+ return;
+ }
+
+ write(hour(), 2);
+ *out++ = ':';
+ write(minute(), 2);
+ }
+
+ void on_iso_time() {
+ on_24_hour_time();
+ *out++ = ':';
+ if (handle_nan_inf()) return;
+ write(second(), 2);
+ }
+
+ void on_am_pm() {
+ if (handle_nan_inf()) return;
+ format_localized(time(), 'p');
+ }
+
+ void on_duration_value() {
+ if (handle_nan_inf()) return;
+ write_sign();
+ out = format_duration_value