summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /tools
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools')
-rw-r--r--tools/CppunitTest_tools_config.mk38
-rw-r--r--tools/CppunitTest_tools_test.mk83
-rw-r--r--tools/CustomTarget_reversemap.mk22
-rw-r--r--tools/Executable_bestreversemap.mk20
-rw-r--r--tools/IwyuFilter_tools.yaml42
-rw-r--r--tools/Library_tl.mk136
-rw-r--r--tools/Makefile7
-rw-r--r--tools/Module_tools.mk38
-rw-r--r--tools/README.md11
-rw-r--r--tools/StaticLibrary_ooopathutils.mk26
-rw-r--r--tools/inc/pch/precompiled_tl.cxx12
-rw-r--r--tools/inc/pch/precompiled_tl.hxx81
-rw-r--r--tools/inc/poly.h83
-rw-r--r--tools/inc/systemdatetime.hxx22
-rw-r--r--tools/qa/cppunit/test_100mm2twips.cxx53
-rw-r--r--tools/qa/cppunit/test_GenericTypeSerializer.cxx144
-rw-r--r--tools/qa/cppunit/test_Wildcard.cxx46
-rw-r--r--tools/qa/cppunit/test_bigint.cxx97
-rw-r--r--tools/qa/cppunit/test_color.cxx252
-rw-r--r--tools/qa/cppunit/test_config.cxx200
-rw-r--r--tools/qa/cppunit/test_cpu_runtime_detection_AVX2.cxx40
-rw-r--r--tools/qa/cppunit/test_cpu_runtime_detection_AVX2_check.cxx56
-rw-r--r--tools/qa/cppunit/test_cpu_runtime_detection_SSE2.cxx40
-rw-r--r--tools/qa/cppunit/test_cpu_runtime_detection_SSE2_check.cxx37
-rw-r--r--tools/qa/cppunit/test_cpu_runtime_detection_SSSE3.cxx40
-rw-r--r--tools/qa/cppunit/test_cpu_runtime_detection_SSSE3_check.cxx37
-rw-r--r--tools/qa/cppunit/test_cpu_runtime_detection_x86_checks.hxx23
-rw-r--r--tools/qa/cppunit/test_cpuid.cxx57
-rw-r--r--tools/qa/cppunit/test_date.cxx564
-rw-r--r--tools/qa/cppunit/test_duration.cxx356
-rw-r--r--tools/qa/cppunit/test_fract.cxx107
-rw-r--r--tools/qa/cppunit/test_fround.cxx62
-rw-r--r--tools/qa/cppunit/test_guid.cxx123
-rw-r--r--tools/qa/cppunit/test_inetmime.cxx167
-rw-r--r--tools/qa/cppunit/test_json_writer.cxx97
-rw-r--r--tools/qa/cppunit/test_pathutils.cxx66
-rw-r--r--tools/qa/cppunit/test_poly.cxx54
-rw-r--r--tools/qa/cppunit/test_rectangle.cxx254
-rw-r--r--tools/qa/cppunit/test_reversemap.cxx154
-rw-r--r--tools/qa/cppunit/test_stream.cxx354
-rw-r--r--tools/qa/cppunit/test_time.cxx154
-rw-r--r--tools/qa/cppunit/test_urlobj.cxx382
-rw-r--r--tools/qa/cppunit/test_xmlwalker.cxx92
-rw-r--r--tools/qa/cppunit/test_xmlwriter.cxx70
-rw-r--r--tools/qa/cppunit/test_zcodec.cxx123
-rw-r--r--tools/qa/data/test.xml13
-rw-r--r--tools/qa/data/testconfig.ini5
-rw-r--r--tools/source/datetime/datetime.cxx291
-rw-r--r--tools/source/datetime/datetimeutils.cxx82
-rw-r--r--tools/source/datetime/duration.cxx328
-rw-r--r--tools/source/datetime/systemdatetime.cxx107
-rw-r--r--tools/source/datetime/tdate.cxx412
-rw-r--r--tools/source/datetime/ttime.cxx506
-rw-r--r--tools/source/debug/debug.cxx57
-rw-r--r--tools/source/fsys/fileutil.cxx100
-rw-r--r--tools/source/fsys/urlobj.cxx4892
-rw-r--r--tools/source/fsys/wldcrd.cxx120
-rw-r--r--tools/source/generic/b3dtrans.cxx456
-rw-r--r--tools/source/generic/bigint.cxx852
-rw-r--r--tools/source/generic/color.cxx266
-rw-r--r--tools/source/generic/config.cxx944
-rw-r--r--tools/source/generic/fract.cxx537
-rw-r--r--tools/source/generic/gen.cxx225
-rw-r--r--tools/source/generic/line.cxx140
-rw-r--r--tools/source/generic/point.cxx86
-rw-r--r--tools/source/generic/poly.cxx1866
-rw-r--r--tools/source/generic/poly2.cxx520
-rw-r--r--tools/source/generic/svborder.cxx35
-rw-r--r--tools/source/inet/inetmime.cxx1387
-rw-r--r--tools/source/inet/inetmsg.cxx292
-rw-r--r--tools/source/inet/inetstrm.cxx296
-rw-r--r--tools/source/memtools/multisel.cxx744
-rw-r--r--tools/source/misc/cpuid.cxx140
-rw-r--r--tools/source/misc/extendapplicationenvironment.cxx88
-rw-r--r--tools/source/misc/fix16.cxx172
-rw-r--r--tools/source/misc/json_writer.cxx405
-rw-r--r--tools/source/misc/pathutils.cxx109
-rw-r--r--tools/source/ref/globname.cxx175
-rw-r--r--tools/source/ref/ref.cxx31
-rw-r--r--tools/source/reversemap/bestreversemap.cxx154
-rw-r--r--tools/source/stream/GenericTypeSerializer.cxx224
-rw-r--r--tools/source/stream/stream.cxx2001
-rw-r--r--tools/source/stream/strmunx.cxx476
-rw-r--r--tools/source/stream/strmwnt.cxx415
-rw-r--r--tools/source/stream/vcompat.cxx68
-rw-r--r--tools/source/string/tenccvt.cxx79
-rw-r--r--tools/source/xml/XmlWalker.cxx116
-rw-r--r--tools/source/xml/XmlWriter.cxx172
-rw-r--r--tools/source/zcodec/zcodec.cxx405
89 files changed, 25711 insertions, 0 deletions
diff --git a/tools/CppunitTest_tools_config.mk b/tools/CppunitTest_tools_config.mk
new file mode 100644
index 0000000000..79c07c7aba
--- /dev/null
+++ b/tools/CppunitTest_tools_config.mk
@@ -0,0 +1,38 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,tools_config))
+
+$(eval $(call gb_CppunitTest_use_external,tools_config,boost_headers))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,tools_config, \
+ tools/qa/cppunit/test_config \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,tools_config))
+
+$(eval $(call gb_CppunitTest_use_libraries,tools_config, \
+ sal \
+ tl \
+ test \
+ unotest \
+))
+
+$(eval $(call gb_CppunitTest_use_static_libraries,tools_config, \
+ ooopathutils \
+))
+
+$(eval $(call gb_CppunitTest_set_include,tools_config,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/tools/inc \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/tools/CppunitTest_tools_test.mk b/tools/CppunitTest_tools_test.mk
new file mode 100644
index 0000000000..1063b778ab
--- /dev/null
+++ b/tools/CppunitTest_tools_test.mk
@@ -0,0 +1,83 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,tools_test))
+
+$(eval $(call gb_CppunitTest_use_external,tools_test,boost_headers))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,tools_test, \
+ tools/qa/cppunit/test_bigint \
+ tools/qa/cppunit/test_date \
+ tools/qa/cppunit/test_time \
+ tools/qa/cppunit/test_duration \
+ tools/qa/cppunit/test_fract \
+ tools/qa/cppunit/test_inetmime \
+ tools/qa/cppunit/test_json_writer \
+ tools/qa/cppunit/test_pathutils \
+ tools/qa/cppunit/test_poly \
+ tools/qa/cppunit/test_reversemap \
+ tools/qa/cppunit/test_stream \
+ tools/qa/cppunit/test_urlobj \
+ tools/qa/cppunit/test_color \
+ tools/qa/cppunit/test_rectangle \
+ tools/qa/cppunit/test_100mm2twips \
+ tools/qa/cppunit/test_fround \
+ tools/qa/cppunit/test_xmlwalker \
+ tools/qa/cppunit/test_xmlwriter \
+ tools/qa/cppunit/test_GenericTypeSerializer \
+ tools/qa/cppunit/test_guid \
+ tools/qa/cppunit/test_cpuid \
+ tools/qa/cppunit/test_cpu_runtime_detection_AVX2 \
+ tools/qa/cppunit/test_cpu_runtime_detection_SSE2 \
+ tools/qa/cppunit/test_cpu_runtime_detection_SSSE3 \
+ tools/qa/cppunit/test_Wildcard \
+ tools/qa/cppunit/test_zcodec \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,tools_test,\
+ tools/qa/cppunit/test_cpu_runtime_detection_AVX2_check, $(CXXFLAGS_INTRINSICS_AVX2) \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,tools_test,\
+ tools/qa/cppunit/test_cpu_runtime_detection_SSE2_check, $(CXXFLAGS_INTRINSICS_SSE2) \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,tools_test,\
+ tools/qa/cppunit/test_cpu_runtime_detection_SSSE3_check, $(CXXFLAGS_INTRINSICS_SSSE3) \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,tools_test))
+
+$(eval $(call gb_CppunitTest_use_libraries,tools_test, \
+ basegfx \
+ comphelper \
+ sal \
+ tl \
+ test \
+ unotest \
+ utl \
+ vcl \
+))
+
+$(eval $(call gb_CppunitTest_use_static_libraries,tools_test, \
+ ooopathutils \
+))
+
+$(eval $(call gb_CppunitTest_set_include,tools_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/tools/inc \
+))
+
+$(eval $(call gb_Library_use_externals,tools_test,\
+ libxml2 \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/tools/CustomTarget_reversemap.mk b/tools/CustomTarget_reversemap.mk
new file mode 100644
index 0000000000..94a697f1a6
--- /dev/null
+++ b/tools/CustomTarget_reversemap.mk
@@ -0,0 +1,22 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CustomTarget_CustomTarget,tools/string))
+
+$(call gb_CustomTarget_get_target,tools/string) : \
+ $(call gb_CustomTarget_get_workdir,tools/string)/reversemap.cxx
+
+$(call gb_CustomTarget_get_workdir,tools/string)/reversemap.cxx : \
+ $(call gb_Executable_get_runtime_dependencies,bestreversemap) \
+ | $(call gb_CustomTarget_get_workdir,tools/string)/.dir
+ $(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),BRM,1)
+ $(call gb_Trace_StartRange,$(subst $(WORKDIR)/,,$@),BRM)
+ $(call gb_Helper_execute,bestreversemap > $@)
+ $(call gb_Trace_EndRange,$(subst $(WORKDIR)/,,$@),BRM)
+
+# vim: set noet sw=4 ts=4:
diff --git a/tools/Executable_bestreversemap.mk b/tools/Executable_bestreversemap.mk
new file mode 100644
index 0000000000..6e06a51ef4
--- /dev/null
+++ b/tools/Executable_bestreversemap.mk
@@ -0,0 +1,20 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_Executable_Executable,bestreversemap))
+
+$(eval $(call gb_Executable_use_libraries,bestreversemap,\
+ sal \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,bestreversemap,\
+ tools/source/reversemap/bestreversemap \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/tools/IwyuFilter_tools.yaml b/tools/IwyuFilter_tools.yaml
new file mode 100644
index 0000000000..5c5dbaaec1
--- /dev/null
+++ b/tools/IwyuFilter_tools.yaml
@@ -0,0 +1,42 @@
+---
+assumeFilename: tools/source/generic/gen.cxx
+excludelist:
+ tools/qa/cppunit/test_pathutils.cxx:
+ # Needed for WIN32 specific unit test
+ - cppunit/TestAssert.h
+ - cppunit/plugin/TestPlugIn.h
+ - tools/pathutils.hxx
+ - cwchar
+ tools/qa/cppunit/test_cpu_runtime_detection_AVX2_check.cxx:
+ # Needed for LO_AVX2_AVAILABLE case
+ - sal/types.h
+ - tools/simdsupport.hxx
+ - stdlib.h
+ tools/qa/cppunit/test_cpu_runtime_detection_SSSE3_check.cxx:
+ # Needed for LO_SSSE3_AVAILABLE case
+ - tools/simdsupport.hxx
+ - stdlib.h
+ tools/source/debug/debug.cxx:
+ # Needed for linker visibility
+ - comphelper/diagnose_ex.hxx
+ # Keep for commented out "if defined __GLIBCXX__" path
+ - cxxabi.h
+ tools/source/misc/extendapplicationenvironment.cxx:
+ # Needed on MACOSX
+ - config_folders.h
+ # Needed for linker visibility
+ - tools/extendapplicationenvironment.hxx
+ tools/source/ref/ref.cxx:
+ # Don't replace with impl. detail
+ - tools/weakbase.hxx
+ tools/source/stream/strmwnt.cxx:
+ # WIN32-specific file
+ - string.h
+ - osl/thread.h
+ - o3tl/char16_t2wchar_t.hxx
+ tools/source/stream/strmunx.cxx:
+ # Needed for OSL_DEBUG_LEVEL > 1
+ - osl/thread.h
+ tools/source/string/tenccvt.cxx:
+ # Needed for linker visibility
+ - tools/tenccvt.hxx
diff --git a/tools/Library_tl.mk b/tools/Library_tl.mk
new file mode 100644
index 0000000000..8269e6ae98
--- /dev/null
+++ b/tools/Library_tl.mk
@@ -0,0 +1,136 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,tl))
+
+$(eval $(call gb_Library_set_include,tl,\
+ -I$(SRCDIR)/tools/inc \
+ $$(INCLUDE) \
+))
+
+$(eval $(call gb_Library_set_precompiled_header,tl,tools/inc/pch/precompiled_tl))
+
+$(eval $(call gb_Library_add_defs,tl,\
+ -DTOOLS_DLLIMPLEMENTATION \
+))
+
+$(eval $(call gb_Library_use_sdk_api,tl))
+
+$(eval $(call gb_Library_use_libraries,tl,\
+ basegfx \
+ comphelper \
+ i18nlangtag \
+ cppu \
+ cppuhelper \
+ sal \
+))
+
+
+$(eval $(call gb_Library_add_exception_objects,tl,\
+ tools/source/datetime/datetime \
+ tools/source/datetime/datetimeutils \
+ tools/source/datetime/duration \
+ tools/source/datetime/systemdatetime \
+ tools/source/datetime/tdate \
+ tools/source/datetime/ttime \
+ tools/source/debug/debug \
+ tools/source/fsys/fileutil \
+ tools/source/fsys/urlobj \
+ tools/source/fsys/wldcrd \
+ tools/source/generic/b3dtrans \
+ tools/source/generic/bigint \
+ tools/source/generic/color \
+ tools/source/generic/config \
+ tools/source/generic/fract \
+ tools/source/generic/gen \
+ tools/source/generic/line \
+ tools/source/generic/point \
+ tools/source/generic/poly \
+ tools/source/generic/poly2 \
+ tools/source/generic/svborder \
+ tools/source/inet/inetmime \
+ tools/source/inet/inetmsg \
+ tools/source/inet/inetstrm \
+ tools/source/memtools/multisel \
+ tools/source/misc/cpuid \
+ tools/source/misc/extendapplicationenvironment \
+ tools/source/misc/json_writer \
+ tools/source/ref/globname \
+ tools/source/ref/ref \
+ tools/source/stream/stream \
+ tools/source/stream/vcompat \
+ tools/source/stream/GenericTypeSerializer \
+ tools/source/string/tenccvt \
+ tools/source/zcodec/zcodec \
+ tools/source/xml/XmlWriter \
+ tools/source/xml/XmlWalker \
+))
+
+ifneq ($(SYSTEM_LIBFIXMATH),TRUE)
+$(eval $(call gb_Library_add_exception_objects,tl,\
+ tools/source/misc/fix16 \
+))
+endif
+
+ifeq ($(OS),WNT)
+$(eval $(call gb_Library_add_exception_objects,tl, \
+ tools/source/stream/strmwnt \
+))
+else
+$(eval $(call gb_Library_add_exception_objects,tl, \
+ tools/source/stream/strmunx \
+))
+endif
+
+$(eval $(call gb_Library_add_generated_exception_objects,tl,\
+ CustomTarget/tools/string/reversemap \
+))
+
+$(eval $(call gb_Library_use_externals,tl,\
+ boost_headers \
+ zlib \
+ libxml2 \
+))
+
+ifeq ($(OS),LINUX)
+$(eval $(call gb_Library_add_libs,tl,\
+ -lrt \
+))
+endif
+
+ifeq ($(SYSTEM_LIBFIXMATH),TRUE)
+$(eval $(call gb_Library_add_libs,tl,\
+ -llibfixmath \
+))
+endif
+
+ifeq ($(OS),WNT)
+
+$(eval $(call gb_Library_use_system_win32_libs,tl,\
+ mpr \
+ netapi32 \
+ ole32 \
+ shell32 \
+ uuid \
+ winmm \
+))
+
+endif
+
+# vim: set noet sw=4 ts=4:
diff --git a/tools/Makefile b/tools/Makefile
new file mode 100644
index 0000000000..ccb1c85a04
--- /dev/null
+++ b/tools/Makefile
@@ -0,0 +1,7 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+
+module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))
+
+include $(module_directory)/../solenv/gbuild/partial_build.mk
+
+# vim: set noet sw=4 ts=4:
diff --git a/tools/Module_tools.mk b/tools/Module_tools.mk
new file mode 100644
index 0000000000..5632a5b94a
--- /dev/null
+++ b/tools/Module_tools.mk
@@ -0,0 +1,38 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+
+$(eval $(call gb_Module_Module,tools))
+
+$(eval $(call gb_Module_add_targets,tools,\
+ CustomTarget_reversemap \
+ Library_tl \
+ StaticLibrary_ooopathutils \
+))
+
+$(eval $(call gb_Module_add_targets_for_build,tools,\
+ Executable_bestreversemap \
+))
+
+$(eval $(call gb_Module_add_check_targets,tools,\
+ CppunitTest_tools_test \
+ CppunitTest_tools_config \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/tools/README.md b/tools/README.md
new file mode 100644
index 0000000000..5bb9f46d67
--- /dev/null
+++ b/tools/README.md
@@ -0,0 +1,11 @@
+# Old Tools (Deprecated)
+
+Predates sal - string functions, url manipulation, stream stuff,
+polygons, etc.
+
+Exact history is lost before Sept. 18th, 2000, but old source code
+comments show that part of the tools library dates back until at least
+April 1991.
+
+This directory will be removed in the near future (see tdf#39445 or
+tdf#63154).
diff --git a/tools/StaticLibrary_ooopathutils.mk b/tools/StaticLibrary_ooopathutils.mk
new file mode 100644
index 0000000000..1891c54e6d
--- /dev/null
+++ b/tools/StaticLibrary_ooopathutils.mk
@@ -0,0 +1,26 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_StaticLibrary_StaticLibrary,ooopathutils))
+
+$(eval $(call gb_StaticLibrary_add_exception_objects,ooopathutils,\
+ tools/source/misc/pathutils \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/tools/inc/pch/precompiled_tl.cxx b/tools/inc/pch/precompiled_tl.cxx
new file mode 100644
index 0000000000..cb05968098
--- /dev/null
+++ b/tools/inc/pch/precompiled_tl.cxx
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "precompiled_tl.hxx"
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/inc/pch/precompiled_tl.hxx b/tools/inc/pch/precompiled_tl.hxx
new file mode 100644
index 0000000000..a0a7a4e372
--- /dev/null
+++ b/tools/inc/pch/precompiled_tl.hxx
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/*
+ This file has been autogenerated by update_pch.sh. It is possible to edit it
+ manually (such as when an include file has been moved/renamed/removed). All such
+ manual changes will be rewritten by the next run of update_pch.sh (which presumably
+ also fixes all possible problems, so it's usually better to use it).
+
+ Generated on 2021-04-06 09:21:31 using:
+ ./bin/update_pch tools tl --cutoff=5 --exclude:system --exclude:module --exclude:local
+
+ If after updating build fails, use the following command to locate conflicting headers:
+ ./bin/update_pch_bisect ./tools/inc/pch/precompiled_tl.hxx "make tools.build" --find-conflicts
+*/
+
+#include <sal/config.h>
+#if PCH_LEVEL >= 1
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <new>
+#include <ostream>
+#include <stddef.h>
+#include <string.h>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include <boost/rational.hpp>
+#endif // PCH_LEVEL >= 1
+#if PCH_LEVEL >= 2
+#include <osl/diagnose.h>
+#include <osl/endian.h>
+#include <osl/file.hxx>
+#include <osl/process.h>
+#include <osl/thread.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/character.hxx>
+#include <rtl/math.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/string.h>
+#include <rtl/string.hxx>
+#include <rtl/stringconcat.hxx>
+#include <rtl/stringutils.hxx>
+#include <rtl/tencinfo.h>
+#include <rtl/textenc.h>
+#include <rtl/ustrbuf.h>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.h>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <sal/macros.h>
+#include <sal/saldllapi.h>
+#include <sal/types.h>
+#include <sal/typesizes.h>
+#endif // PCH_LEVEL >= 2
+#if PCH_LEVEL >= 3
+#include <basegfx/basegfxdllapi.h>
+#include <basegfx/vector/b2enums.hxx>
+#include <o3tl/cow_wrapper.hxx>
+#endif // PCH_LEVEL >= 3
+#if PCH_LEVEL >= 4
+#include <tools/debug.hxx>
+#include <tools/gen.hxx>
+#include <tools/long.hxx>
+#include <tools/stream.hxx>
+#include <tools/toolsdllapi.h>
+#endif // PCH_LEVEL >= 4
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/inc/poly.h b/tools/inc/poly.h
new file mode 100644
index 0000000000..772e854e31
--- /dev/null
+++ b/tools/inc/poly.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <sal/types.h>
+#include <memory>
+#include <tools/poly.hxx>
+
+class SAL_WARN_UNUSED ImplPolygon
+{
+public:
+ std::unique_ptr<Point[]> mxPointAry;
+ std::unique_ptr<PolyFlags[]> mxFlagAry;
+ sal_uInt16 mnPoints;
+
+public:
+ ImplPolygon() : mnPoints(0) {}
+ ImplPolygon( sal_uInt16 nInitSize );
+ ImplPolygon( sal_uInt16 nPoints, const Point* pPtAry, const PolyFlags* pInitFlags );
+ ImplPolygon( const ImplPolygon& rImplPoly );
+ ImplPolygon( const tools::Rectangle& rRect );
+ ImplPolygon( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound);
+ ImplPolygon( const Point& rCenter, tools::Long nRadX, tools::Long nRadY );
+ ImplPolygon( const tools::Rectangle& rBound, const Point& rStart, const Point& rEnd,
+ PolyStyle eStyle, bool bClockWiseArcDirection );
+ ImplPolygon( const Point& rBezPt1, const Point& rCtrlPt1, const Point& rBezPt2,
+ const Point& rCtrlPt2, sal_uInt16 nPoints );
+ ImplPolygon(const basegfx::B2DPolygon& rPolygon);
+
+ bool operator==( const ImplPolygon& rCandidate ) const;
+
+ void ImplInitSize(sal_uInt16 nInitSize, bool bFlags = false);
+ void ImplSetSize( sal_uInt16 nSize, bool bResize = true );
+ void ImplCreateFlagArray();
+ bool ImplSplit( sal_uInt16 nPos, sal_uInt16 nSpace, ImplPolygon const * pInitPoly = nullptr );
+};
+
+#define MAX_POLYGONS SAL_MAX_UINT16
+
+struct ImplPolyPolygon
+{
+ std::vector<tools::Polygon> mvPolyAry;
+
+ ImplPolyPolygon( sal_uInt16 nInitSize )
+ {
+ if ( !nInitSize )
+ nInitSize = 1;
+ mvPolyAry.reserve(nInitSize);
+ }
+
+ ImplPolyPolygon( const tools::Polygon& rPoly )
+ {
+ if ( rPoly.GetSize() )
+ mvPolyAry.push_back(rPoly);
+ else
+ mvPolyAry.reserve(16);
+ }
+
+ ImplPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon);
+
+ bool operator==(ImplPolyPolygon const & other) const
+ {
+ return mvPolyAry == other.mvPolyAry;
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/inc/systemdatetime.hxx b/tools/inc/systemdatetime.hxx
new file mode 100644
index 0000000000..1b04e26ade
--- /dev/null
+++ b/tools/inc/systemdatetime.hxx
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#pragma once
+#include <sal/types.h>
+
+constexpr sal_Int64 SEC_MASK = SAL_CONST_INT64(1000000000);
+constexpr sal_Int64 MIN_MASK = SAL_CONST_INT64(100000000000);
+constexpr sal_Int64 HOUR_MASK = SAL_CONST_INT64(10000000000000);
+
+/** Get current local timestamp.
+ Both pDate and pTime can be null.
+ Returns true if succeeded, false otherwise.
+ */
+bool GetSystemDateTime(sal_Int32* pDate, sal_Int64* pTime);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/qa/cppunit/test_100mm2twips.cxx b/tools/qa/cppunit/test_100mm2twips.cxx
new file mode 100644
index 0000000000..1fb69d9820
--- /dev/null
+++ b/tools/qa/cppunit/test_100mm2twips.cxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/types.h>
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/UnitConversion.hxx>
+
+class UnitConversionTest : public CppUnit::TestFixture
+{
+public:
+ void testSanitiseMm100ToTwip()
+ {
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(145), sanitiseMm100ToTwip(255));
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(-145), sanitiseMm100ToTwip(-255));
+ }
+
+ void testConvertPointToMm100()
+ {
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(599.72, convertPointToMm100(17.0), 1E-2);
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(600), convertPointToMm100(sal_Int64(17)));
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(22930.55, convertPointToMm100(650.0), 1E-2);
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(22931), convertPointToMm100(sal_Int64(650)));
+ }
+
+ CPPUNIT_TEST_SUITE(UnitConversionTest);
+ CPPUNIT_TEST(testSanitiseMm100ToTwip);
+ CPPUNIT_TEST(testConvertPointToMm100);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(UnitConversionTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_GenericTypeSerializer.cxx b/tools/qa/cppunit/test_GenericTypeSerializer.cxx
new file mode 100644
index 0000000000..d378dd7c30
--- /dev/null
+++ b/tools/qa/cppunit/test_GenericTypeSerializer.cxx
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/types.h>
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/GenericTypeSerializer.hxx>
+#include <tools/stream.hxx>
+#include <tools/gen.hxx>
+
+namespace tools
+{
+class GenericTypeSerializerTest : public CppUnit::TestFixture
+{
+public:
+ void testRoundtripPoint()
+ {
+ Point aPoint(20, 50);
+ SvMemoryStream aStream;
+ GenericTypeSerializer aSerializer(aStream);
+ aSerializer.writePoint(aPoint);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ Point aReadPoint;
+ aSerializer.readPoint(aReadPoint);
+ CPPUNIT_ASSERT_EQUAL(aPoint, aReadPoint);
+ }
+
+ void testRoundtripSize()
+ {
+ Size aSize(40, 80);
+ SvMemoryStream aStream;
+ GenericTypeSerializer aSerializer(aStream);
+ aSerializer.writeSize(aSize);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ Size aReadSize;
+ aSerializer.readSize(aReadSize);
+ CPPUNIT_ASSERT_EQUAL(aSize, aReadSize);
+ }
+
+ void testRoundtripRectangle()
+ {
+ {
+ Rectangle aRectangle;
+ CPPUNIT_ASSERT(aRectangle.IsEmpty());
+ SvMemoryStream aStream;
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ GenericTypeSerializer aSerializer(aStream);
+ aSerializer.writeRectangle(aRectangle);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ // Need to set the rectangle to non-empty, so it will be set to empty later
+ Rectangle aReadRectangle(Point(20, 50), Size(10, 30));
+ aSerializer.readRectangle(aReadRectangle);
+ CPPUNIT_ASSERT(aRectangle.IsEmpty());
+ }
+
+ {
+ Rectangle aRectangle(Point(20, 50), Size(10, 30));
+ SvMemoryStream aStream;
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ GenericTypeSerializer aSerializer(aStream);
+ aSerializer.writeRectangle(aRectangle);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ Rectangle aReadRectangle;
+ aSerializer.readRectangle(aReadRectangle);
+ CPPUNIT_ASSERT_EQUAL(aRectangle.Top(), aReadRectangle.Top());
+ CPPUNIT_ASSERT_EQUAL(aRectangle.Left(), aReadRectangle.Left());
+ CPPUNIT_ASSERT_EQUAL(aRectangle.Right(), aReadRectangle.Right());
+ CPPUNIT_ASSERT_EQUAL(aRectangle.Bottom(), aReadRectangle.Bottom());
+ }
+ }
+
+ void testRoundtripFraction()
+ {
+ {
+ Fraction aFraction(2, 5);
+ CPPUNIT_ASSERT_EQUAL(true, aFraction.IsValid());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aFraction.GetNumerator());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(5), aFraction.GetDenominator());
+
+ SvMemoryStream aStream;
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ GenericTypeSerializer aSerializer(aStream);
+ aSerializer.writeFraction(aFraction);
+
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ Fraction aReadFraction(1, 2);
+ aSerializer.readFraction(aReadFraction);
+ CPPUNIT_ASSERT_EQUAL(true, aReadFraction.IsValid());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aReadFraction.GetNumerator());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(5), aReadFraction.GetDenominator());
+ }
+ {
+ Fraction aFraction(1, 0);
+ CPPUNIT_ASSERT_EQUAL(false, aFraction.IsValid());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aFraction.GetNumerator());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(-1), aFraction.GetDenominator());
+
+ SvMemoryStream aStream;
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ GenericTypeSerializer aSerializer(aStream);
+ aSerializer.writeFraction(aFraction);
+
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ Fraction aReadFraction(1, 2);
+ aSerializer.readFraction(aReadFraction);
+ CPPUNIT_ASSERT_EQUAL(false, aReadFraction.IsValid());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aReadFraction.GetNumerator());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(-1), aReadFraction.GetDenominator());
+ }
+ }
+
+ CPPUNIT_TEST_SUITE(GenericTypeSerializerTest);
+ CPPUNIT_TEST(testRoundtripPoint);
+ CPPUNIT_TEST(testRoundtripSize);
+ CPPUNIT_TEST(testRoundtripRectangle);
+ CPPUNIT_TEST(testRoundtripFraction);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(GenericTypeSerializerTest);
+
+} // namespace tools
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_Wildcard.cxx b/tools/qa/cppunit/test_Wildcard.cxx
new file mode 100644
index 0000000000..1760ca6932
--- /dev/null
+++ b/tools/qa/cppunit/test_Wildcard.cxx
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <sal/types.h>
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <tools/wldcrd.hxx>
+
+namespace
+{
+class Test : public CppUnit::TestFixture
+{
+public:
+ void test_Wildcard();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(test_Wildcard);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void Test::test_Wildcard()
+{
+ WildCard wildcard(u"*.html;*??a;*\\*abc;*\\?xyz", ';'); // tdf#148253
+ CPPUNIT_ASSERT(wildcard.Matches(u"foo.html"));
+ CPPUNIT_ASSERT(wildcard.Matches(u"foo.ht.html")); // test stepping back after partial match
+ CPPUNIT_ASSERT(wildcard.Matches(u"foo.html.html")); // test stepping back after full match
+ CPPUNIT_ASSERT(wildcard.Matches(u"??aa")); // test stepping back with question marks
+ CPPUNIT_ASSERT(wildcard.Matches(u"111*abc")); // test escaped asterisk
+ CPPUNIT_ASSERT(!wildcard.Matches(u"111-abc")); // test escaped asterisk
+ CPPUNIT_ASSERT(wildcard.Matches(u"111?xyz")); // test escaped question mark
+ CPPUNIT_ASSERT(!wildcard.Matches(u"111-xyz")); // test escaped question mark
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/qa/cppunit/test_bigint.cxx b/tools/qa/cppunit/test_bigint.cxx
new file mode 100644
index 0000000000..3c7740fb76
--- /dev/null
+++ b/tools/qa/cppunit/test_bigint.cxx
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/bigint.hxx>
+
+#include <limits>
+
+namespace tools
+{
+class BigIntTest : public CppUnit::TestFixture
+{
+public:
+ void testConstructionFromLongLong();
+
+ CPPUNIT_TEST_SUITE(BigIntTest);
+ CPPUNIT_TEST(testConstructionFromLongLong);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void BigIntTest::testConstructionFromLongLong()
+{
+ // small positive number
+ {
+ BigInt bi(static_cast<sal_Int64>(42));
+ CPPUNIT_ASSERT(!bi.IsZero());
+ CPPUNIT_ASSERT(!bi.IsNeg());
+ CPPUNIT_ASSERT(bi.IsLong());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(42), static_cast<sal_Int32>(bi));
+ }
+
+ // small negative number
+ {
+ BigInt bi(static_cast<sal_Int64>(-42));
+ CPPUNIT_ASSERT(!bi.IsZero());
+ CPPUNIT_ASSERT(bi.IsNeg());
+ CPPUNIT_ASSERT(bi.IsLong());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-42), static_cast<sal_Int32>(bi));
+ }
+
+ // positive number just fitting to sal_Int32
+ {
+ BigInt bi(static_cast<sal_Int64>(std::numeric_limits<sal_Int32>::max()));
+ CPPUNIT_ASSERT(!bi.IsZero());
+ CPPUNIT_ASSERT(!bi.IsNeg());
+ CPPUNIT_ASSERT(bi.IsLong());
+ CPPUNIT_ASSERT_EQUAL(std::numeric_limits<sal_Int32>::max(), static_cast<sal_Int32>(bi));
+ }
+
+ // negative number just fitting to sal_Int32
+ {
+ BigInt bi(static_cast<sal_Int64>(std::numeric_limits<sal_Int32>::min()));
+ CPPUNIT_ASSERT(!bi.IsZero());
+ CPPUNIT_ASSERT(bi.IsNeg());
+ CPPUNIT_ASSERT(bi.IsLong());
+ CPPUNIT_ASSERT_EQUAL(std::numeric_limits<sal_Int32>::min(), static_cast<sal_Int32>(bi));
+ }
+
+ // positive number not fitting to sal_Int32
+ {
+ BigInt bi(static_cast<sal_Int64>(std::numeric_limits<sal_Int32>::max()) + 1);
+ CPPUNIT_ASSERT(!bi.IsZero());
+ CPPUNIT_ASSERT(!bi.IsNeg());
+ CPPUNIT_ASSERT(!bi.IsLong());
+ }
+
+ // negative number not fitting to sal_Int32
+ {
+ BigInt bi(static_cast<sal_Int64>(std::numeric_limits<sal_Int32>::min()) - 1);
+ CPPUNIT_ASSERT(!bi.IsZero());
+ CPPUNIT_ASSERT(bi.IsNeg());
+ CPPUNIT_ASSERT(!bi.IsLong());
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BigIntTest);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_color.cxx b/tools/qa/cppunit/test_color.cxx
new file mode 100644
index 0000000000..02f23fb3b8
--- /dev/null
+++ b/tools/qa/cppunit/test_color.cxx
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/types.h>
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <tools/color.hxx>
+
+namespace
+{
+
+class Test: public CppUnit::TestFixture
+{
+public:
+ void testVariables();
+ void test_asRGBColor();
+ void test_ApplyTintOrShade();
+ void test_ApplyLumModOff();
+ void testGetColorError();
+ void testInvert();
+ void testBColor();
+ void testLuminance();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(testVariables);
+ CPPUNIT_TEST(test_asRGBColor);
+ CPPUNIT_TEST(test_ApplyTintOrShade);
+ CPPUNIT_TEST(test_ApplyLumModOff);
+ CPPUNIT_TEST(testGetColorError);
+ CPPUNIT_TEST(testInvert);
+ CPPUNIT_TEST(testBColor);
+ CPPUNIT_TEST(testLuminance);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void Test::testVariables()
+{
+ Color aColor(0x44, 0x88, 0xAA);
+ CPPUNIT_ASSERT_EQUAL(int(0x00), int(255 - aColor.GetAlpha()));
+ CPPUNIT_ASSERT_EQUAL(int(0x44), int(aColor.GetRed()));
+ CPPUNIT_ASSERT_EQUAL(int(0x88), int(aColor.GetGreen()));
+ CPPUNIT_ASSERT_EQUAL(int(0xAA), int(aColor.GetBlue()));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x004488AA), sal_uInt32(aColor));
+
+ aColor = Color(ColorTransparency, 0xAABBCCDD);
+ CPPUNIT_ASSERT_EQUAL(int(0xAA), int(255 - aColor.GetAlpha()));
+ CPPUNIT_ASSERT_EQUAL(int(0xBB), int(aColor.GetRed()));
+ CPPUNIT_ASSERT_EQUAL(int(0xCC), int(aColor.GetGreen()));
+ CPPUNIT_ASSERT_EQUAL(int(0xDD), int(aColor.GetBlue()));
+
+ aColor.SetAlpha(255 - 0x11);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x11BBCCDD), sal_uInt32(aColor));
+
+ aColor.SetRed(0x22);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x1122CCDD), sal_uInt32(aColor));
+
+ aColor.SetGreen(0x33);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x112233DD), sal_uInt32(aColor));
+
+ aColor.SetBlue(0x44);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x11223344), sal_uInt32(aColor));
+
+ aColor.SetAlpha(255 - 0x77);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x77223344), sal_uInt32(aColor));
+
+ aColor.SetRed(0x88);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x77883344), sal_uInt32(aColor));
+
+ aColor.SetGreen(0x99);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x77889944), sal_uInt32(aColor));
+
+ aColor.SetBlue(0xAA);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(0x778899AA), sal_uInt32(aColor));
+}
+
+void Test::test_asRGBColor()
+{
+ Color aColor;
+ aColor = COL_BLACK;
+ CPPUNIT_ASSERT_EQUAL(OUString("000000"), aColor.AsRGBHexString());
+
+ aColor = COL_WHITE;
+ CPPUNIT_ASSERT_EQUAL(OUString("ffffff"), aColor.AsRGBHexString());
+
+ aColor = COL_RED;
+ CPPUNIT_ASSERT_EQUAL(OUString("800000"), aColor.AsRGBHexString());
+
+ aColor = COL_TRANSPARENT;
+ CPPUNIT_ASSERT_EQUAL(OUString("ffffff"), aColor.AsRGBHexString());
+
+ aColor = COL_BLUE;
+ CPPUNIT_ASSERT_EQUAL(OUString("000080"), aColor.AsRGBHexString());
+
+ aColor.SetRed(0x12);
+ aColor.SetGreen(0x34);
+ aColor.SetBlue(0x56);
+ CPPUNIT_ASSERT_EQUAL(OUString("123456"), aColor.AsRGBHexString());
+
+ aColor = COL_AUTO;
+ CPPUNIT_ASSERT_EQUAL(OUString("ffffff"), aColor.AsRGBHexString());
+}
+
+OUString createTintShade(sal_uInt8 nR, sal_uInt8 nG, sal_uInt8 nB, std::u16string_view sReference, sal_Int16 nTintShade)
+{
+ Color aColor(nR, nG, nB);
+ if (sReference != aColor.AsRGBHexString())
+ return OUString();
+ aColor.ApplyTintOrShade(nTintShade);
+ return aColor.AsRGBHexString();
+}
+
+void Test::test_ApplyTintOrShade()
+{
+ // BLACK reference
+
+ // 5% tint
+ CPPUNIT_ASSERT_EQUAL(OUString("0d0d0d"), createTintShade(0x00, 0x00, 0x00, u"000000", 500));
+ // 15% tint
+ CPPUNIT_ASSERT_EQUAL(OUString("262626"), createTintShade(0x00, 0x00, 0x00, u"000000", 1500));
+ // 25% tint
+ CPPUNIT_ASSERT_EQUAL(OUString("404040"), createTintShade(0x00, 0x00, 0x00, u"000000", 2500));
+ // 50% tint
+ CPPUNIT_ASSERT_EQUAL(OUString("808080"), createTintShade(0x00, 0x00, 0x00, u"000000", 5000));
+ // 100% tint
+ CPPUNIT_ASSERT_EQUAL(OUString("ffffff"), createTintShade(0x00, 0x00, 0x00, u"000000", 10000));
+
+ // WHITE reference
+
+ // 5% shade
+ CPPUNIT_ASSERT_EQUAL(OUString("f2f2f2"), createTintShade(0xff, 0xff, 0xff, u"ffffff", -500));
+ // 15% shade
+ CPPUNIT_ASSERT_EQUAL(OUString("d9d9d9"), createTintShade(0xff, 0xff, 0xff, u"ffffff", -1500));
+ // 25% shade
+ CPPUNIT_ASSERT_EQUAL(OUString("bfbfbf"), createTintShade(0xff, 0xff, 0xff, u"ffffff", -2500));
+ // 50% shade
+ CPPUNIT_ASSERT_EQUAL(OUString("808080"), createTintShade(0xff, 0xff, 0xff, u"ffffff", -5000));
+ // 100% shade
+ CPPUNIT_ASSERT_EQUAL(OUString("000000"), createTintShade(0xff, 0xff, 0xff, u"ffffff", -10000));
+
+ // GREY reference
+
+ // 0% - no change
+ CPPUNIT_ASSERT_EQUAL(OUString("808080"), createTintShade(0x80, 0x80, 0x80, u"808080", 0));
+
+ // 25% tint
+ CPPUNIT_ASSERT_EQUAL(OUString("a0a0a0"), createTintShade(0x80, 0x80, 0x80, u"808080", 2500));
+ // 50% tint
+ //CPPUNIT_ASSERT_EQUAL(OUString("c0c0c0"), createTintShade(0x80, 0x80, 0x80, "808080", 5000));
+ // disable for now - a rounding error happens on come platforms...
+ // 100% tint
+ CPPUNIT_ASSERT_EQUAL(OUString("ffffff"), createTintShade(0x80, 0x80, 0x80, u"808080", 10000));
+
+ // 25% shade
+ CPPUNIT_ASSERT_EQUAL(OUString("606060"), createTintShade(0x80, 0x80, 0x80, u"808080", -2500));
+ // 50% shade
+ CPPUNIT_ASSERT_EQUAL(OUString("404040"), createTintShade(0x80, 0x80, 0x80, u"808080", -5000));
+ // 100% shade
+ CPPUNIT_ASSERT_EQUAL(OUString("000000"), createTintShade(0x80, 0x80, 0x80, u"808080", -10000));
+}
+
+void Test::test_ApplyLumModOff()
+{
+ // Kind of blue.
+ Color aColor(0x44, 0x72, 0xC4);
+
+ // PowerPoint calls this "Lighter 40%".
+ aColor.ApplyLumModOff(6000, 4000);
+
+ CPPUNIT_ASSERT_EQUAL(OUString("8faadc"), aColor.AsRGBHexString());
+}
+
+void Test::testGetColorError()
+{
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(0), Color(0xAA, 0xBB, 0xCC).GetColorError(Color(0xAA, 0xBB, 0xCC)));
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB0, 0xC0)));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA0, 0xB1, 0xC0)));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA0, 0xB0, 0xC1)));
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB1, 0xC0)));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA0, 0xB1, 0xC1)));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB0, 0xC1)));
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(3), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB1, 0xC1)));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(3), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB1, 0xC1)));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(3), Color(0xA0, 0xB0, 0xC0).GetColorError(Color(0xA1, 0xB1, 0xC1)));
+}
+
+void Test::testInvert()
+{
+ Color aColor(0xFF, 0x00, 0x88);
+ aColor.Invert();
+ CPPUNIT_ASSERT_EQUAL(Color(0x00, 0xFF, 0x77).AsRGBHexString(), aColor.AsRGBHexString());
+
+ // Alpha should be unaffected
+ aColor = Color(ColorTransparency, 0x22, 0xFF, 0x00, 0x88);
+ aColor.Invert();
+ CPPUNIT_ASSERT_EQUAL(Color(ColorTransparency, 0x22, 0x00, 0xFF, 0x77).AsRGBHexString(), aColor.AsRGBHexString());
+}
+
+void Test::testBColor()
+{
+ Color aColor;
+
+ aColor = Color(basegfx::BColor(0.0, 0.0, 0.0));
+
+ CPPUNIT_ASSERT_EQUAL(Color(0x00, 0x00, 0x00).AsRGBHexString(), aColor.AsRGBHexString());
+ CPPUNIT_ASSERT_EQUAL(0.0, aColor.getBColor().getRed());
+ CPPUNIT_ASSERT_EQUAL(0.0, aColor.getBColor().getGreen());
+ CPPUNIT_ASSERT_EQUAL(0.0, aColor.getBColor().getBlue());
+
+ aColor = Color(basegfx::BColor(1.0, 1.0, 1.0));
+
+ CPPUNIT_ASSERT_EQUAL(Color(0xFF, 0xFF, 0xFF).AsRGBHexString(), aColor.AsRGBHexString());
+ CPPUNIT_ASSERT_EQUAL(1.0, aColor.getBColor().getRed());
+ CPPUNIT_ASSERT_EQUAL(1.0, aColor.getBColor().getGreen());
+ CPPUNIT_ASSERT_EQUAL(1.0, aColor.getBColor().getBlue());
+
+ aColor = Color(basegfx::BColor(0.5, 0.25, 0.125));
+
+ CPPUNIT_ASSERT_EQUAL(Color(0x80, 0x40, 0x20).AsRGBHexString(), aColor.AsRGBHexString());
+ // FP error is rather big, but that's normal
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.500, aColor.getBColor().getRed(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.250, aColor.getBColor().getGreen(), 1E-2);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.125, aColor.getBColor().getBlue(), 1E-2);
+
+}
+
+void Test::testLuminance()
+{
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), COL_BLACK.GetLuminance());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(255), COL_WHITE.GetLuminance());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(128), Color(128, 128, 128).GetLuminance());
+ CPPUNIT_ASSERT(COL_WHITE.IsBright());
+ CPPUNIT_ASSERT(COL_BLACK.IsDark());
+ CPPUNIT_ASSERT(Color(249, 250, 251).IsBright());
+ CPPUNIT_ASSERT(Color(9, 10, 11).IsDark());
+ CPPUNIT_ASSERT(COL_WHITE.GetLuminance() > COL_BLACK.GetLuminance());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_config.cxx b/tools/qa/cppunit/test_config.cxx
new file mode 100644
index 0000000000..d54a2a93d5
--- /dev/null
+++ b/tools/qa/cppunit/test_config.cxx
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <test/bootstrapfixture.hxx>
+
+#include <osl/file.hxx>
+#include <rtl/ustring.hxx>
+
+#include <tools/config.hxx>
+
+class ToolsConfigTest : public test::BootstrapFixture
+{
+public:
+ ToolsConfigTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ virtual void setUp() override
+ {
+ maOriginalConfigFile = m_directories.getURLFromSrc(u"/tools/qa/data/");
+ maOriginalConfigFile += "testconfig.ini";
+
+ auto const e = osl::FileBase::getTempDirURL(maConfigFile);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("cannot create temp folder", osl::File::RC::E_None, e);
+ maConfigFile += "/config.ini";
+
+ osl::File::copy(maOriginalConfigFile, maConfigFile);
+ }
+
+ virtual void tearDown() override { osl::File::remove(maConfigFile); }
+
+ void testHasGroup()
+ {
+ Config aConfig(maConfigFile);
+ CPPUNIT_ASSERT(aConfig.HasGroup("TestGroup"));
+ CPPUNIT_ASSERT(aConfig.HasGroup("TestGroup2"));
+ }
+
+ void testGetGroup()
+ {
+ Config aConfig(maConfigFile);
+ CPPUNIT_ASSERT_EQUAL(""_ostr, aConfig.GetGroup());
+
+ CPPUNIT_ASSERT_EQUAL("TestGroup"_ostr, aConfig.GetGroupName(0));
+ CPPUNIT_ASSERT_EQUAL("TestGroup2"_ostr, aConfig.GetGroupName(1));
+ CPPUNIT_ASSERT_EQUAL(""_ostr, aConfig.GetGroupName(2));
+ }
+
+ void testSetGroup()
+ {
+ Config aConfig(maConfigFile);
+
+ aConfig.SetGroup("TestGroup"_ostr);
+ CPPUNIT_ASSERT_EQUAL("TestGroup"_ostr, aConfig.GetGroup());
+
+ // so this is a quirk of Config - you can set the group name,
+ // but it might not exist so you really should first check if
+ // it exists via HasGroup()
+ aConfig.SetGroup("TestGroupA"_ostr);
+ CPPUNIT_ASSERT(!aConfig.HasGroup("TestGroupA"));
+ CPPUNIT_ASSERT_EQUAL("TestGroupA"_ostr, aConfig.GetGroup());
+ }
+
+ void testDeleteGroup()
+ {
+ {
+ Config aConfig(maConfigFile);
+
+ aConfig.DeleteGroup("TestGroup");
+ CPPUNIT_ASSERT(!aConfig.HasGroup("TestGroup"));
+ CPPUNIT_ASSERT_EQUAL("TestGroup2"_ostr, aConfig.GetGroupName(0));
+
+ sal_uInt16 nActual = aConfig.GetGroupCount();
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), nActual);
+ }
+
+ osl::File::copy(maOriginalConfigFile, maConfigFile);
+
+ {
+ Config aConfig(maConfigFile);
+
+ CPPUNIT_ASSERT(!aConfig.HasGroup("NonExistentTestGroup"));
+ aConfig.DeleteGroup("NonExistentTestGroup");
+ CPPUNIT_ASSERT_EQUAL("TestGroup"_ostr, aConfig.GetGroupName(0));
+
+ sal_uInt16 nActual = aConfig.GetGroupCount();
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), nActual);
+ }
+
+ osl::File::copy(maOriginalConfigFile, maConfigFile);
+ }
+
+ void testGetGroupCount()
+ {
+ Config aConfig(maConfigFile);
+ sal_uInt16 nActual = aConfig.GetGroupCount();
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(2), nActual);
+ }
+
+ void testReadKey()
+ {
+ Config aConfig(maConfigFile);
+ aConfig.SetGroup("TestGroup"_ostr);
+ CPPUNIT_ASSERT_EQUAL("testvalue"_ostr, aConfig.ReadKey("testkey"_ostr));
+ CPPUNIT_ASSERT_EQUAL(OString(), aConfig.ReadKey("nonexistenttestkey"_ostr));
+ CPPUNIT_ASSERT_EQUAL("notexists"_ostr,
+ aConfig.ReadKey("nonexistenttestkey"_ostr, "notexists"_ostr));
+
+ aConfig.SetGroup("TestGroup2"_ostr);
+ CPPUNIT_ASSERT_EQUAL("testvalue"_ostr, aConfig.ReadKey("testkey2"_ostr));
+ CPPUNIT_ASSERT_EQUAL(OString(), aConfig.ReadKey("nonexistenttestkey"_ostr));
+ CPPUNIT_ASSERT_EQUAL("notexists"_ostr,
+ aConfig.ReadKey("nonexistenttestkey"_ostr, "notexists"_ostr));
+ }
+
+ void testGetKeyName()
+ {
+ Config aConfig(maConfigFile);
+ aConfig.SetGroup("TestGroup"_ostr);
+ CPPUNIT_ASSERT_EQUAL("testkey"_ostr, aConfig.GetKeyName(0));
+
+ aConfig.SetGroup("TestGroup2"_ostr);
+ CPPUNIT_ASSERT_EQUAL("testkey2"_ostr, aConfig.GetKeyName(0));
+ }
+
+ void testWriteDeleteKey()
+ {
+ Config aConfig(maConfigFile);
+ aConfig.SetGroup("TestGroup"_ostr);
+ aConfig.WriteKey("testkey_new"_ostr, "testvalue"_ostr);
+
+ sal_uInt16 nExpected = 2;
+ sal_uInt16 nActual = aConfig.GetKeyCount();
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+ CPPUNIT_ASSERT_EQUAL("testvalue"_ostr, aConfig.ReadKey("testkey_new"_ostr));
+
+ aConfig.DeleteKey("testkey_new");
+
+ nExpected = 1;
+ nActual = aConfig.GetKeyCount();
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+ CPPUNIT_ASSERT_EQUAL(OString(), aConfig.ReadKey("testkey_new"_ostr));
+
+ aConfig.SetGroup("TestGroup2"_ostr);
+ aConfig.WriteKey("testkey_new"_ostr, "testvalue"_ostr);
+
+ nActual = aConfig.GetKeyCount();
+ nExpected = 2;
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+ CPPUNIT_ASSERT_EQUAL("testvalue"_ostr, aConfig.ReadKey("testkey_new"_ostr));
+
+ aConfig.DeleteKey("testkey_new");
+
+ nActual = aConfig.GetKeyCount();
+ nExpected = 1;
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+ CPPUNIT_ASSERT_EQUAL(OString(), aConfig.ReadKey("testkey_new"_ostr));
+
+ aConfig.SetGroup("TestGroup3"_ostr);
+ aConfig.WriteKey("testkey_new_group3"_ostr, "testvalue"_ostr);
+
+ nActual = aConfig.GetKeyCount();
+ nExpected = 1;
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+ CPPUNIT_ASSERT_EQUAL("testvalue"_ostr, aConfig.ReadKey("testkey_new_group3"_ostr));
+
+ nExpected = 3;
+ CPPUNIT_ASSERT_EQUAL(nExpected, aConfig.GetGroupCount());
+
+ osl::File::copy(maOriginalConfigFile, maConfigFile);
+ }
+
+ CPPUNIT_TEST_SUITE(ToolsConfigTest);
+ CPPUNIT_TEST(testHasGroup);
+ CPPUNIT_TEST(testGetGroup);
+ CPPUNIT_TEST(testSetGroup);
+ CPPUNIT_TEST(testDeleteGroup);
+ CPPUNIT_TEST(testReadKey);
+ CPPUNIT_TEST(testGetGroupCount);
+ CPPUNIT_TEST(testGetKeyName);
+ CPPUNIT_TEST(testWriteDeleteKey);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ OUString maOriginalConfigFile;
+ OUString maConfigFile;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ToolsConfigTest);
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_AVX2.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_AVX2.cxx
new file mode 100644
index 0000000000..5ba8ed9ee2
--- /dev/null
+++ b/tools/qa/cppunit/test_cpu_runtime_detection_AVX2.cxx
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "test_cpu_runtime_detection_x86_checks.hxx"
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/cpuid.hxx>
+
+namespace
+{
+class CpuRuntimeDetection_AVX2 : public CppUnit::TestFixture
+{
+public:
+ void testCpuRuntimeDetection();
+
+ CPPUNIT_TEST_SUITE(CpuRuntimeDetection_AVX2);
+ CPPUNIT_TEST(testCpuRuntimeDetection);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void CpuRuntimeDetection_AVX2::testCpuRuntimeDetection()
+{
+ // can only run this function if CPU supports AVX2
+ if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::AVX2))
+ CpuRuntimeDetectionX86Checks::checkAVX2();
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(CpuRuntimeDetection_AVX2);
+
+} // end anonymous namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_AVX2_check.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_AVX2_check.cxx
new file mode 100644
index 0000000000..7ed8495f36
--- /dev/null
+++ b/tools/qa/cppunit/test_cpu_runtime_detection_AVX2_check.cxx
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/types.h>
+#include <tools/simdsupport.hxx>
+
+#include <stdlib.h>
+
+#include "test_cpu_runtime_detection_x86_checks.hxx"
+
+/* WARNING: This file is compiled with AVX2 support, don't call
+ * any function without checking cpuid to check the CPU can actually
+ * handle it, and don't include any headers that contain templates
+ * or inline functions, which includes cppunit.
+ */
+#ifdef LO_AVX2_AVAILABLE
+#define CPPUNIT_ASSERT_EQUAL(a, b) ((a) == (b) ? (void)0 : abort())
+#endif
+
+void CpuRuntimeDetectionX86Checks::checkAVX2()
+{
+#ifdef LO_AVX2_AVAILABLE
+ __m256i a = _mm256_set_epi64x(1, 4, 8, 3);
+ __m256i b = _mm256_set_epi64x(2, 1, 1, 5);
+ __m256i c = _mm256_xor_si256(a, b);
+
+ sal_Int64 values[4];
+ _mm256_storeu_si256(reinterpret_cast<__m256i*>(&values), c);
+
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(6), values[0]);
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(9), values[1]);
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(5), values[2]);
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(3), values[3]);
+
+ __m256i d = _mm256_set_epi64x(3, 5, 1, 0);
+
+ __m256i result = _mm256_cmpeq_epi64(d, c);
+
+ // Compare equals
+ sal_Int64 compare[4];
+ _mm256_storeu_si256(reinterpret_cast<__m256i*>(&compare), result);
+
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(0), compare[0]);
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(0), compare[1]);
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(-1), compare[2]);
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(-1), compare[3]);
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_SSE2.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_SSE2.cxx
new file mode 100644
index 0000000000..93bac6f2a1
--- /dev/null
+++ b/tools/qa/cppunit/test_cpu_runtime_detection_SSE2.cxx
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "test_cpu_runtime_detection_x86_checks.hxx"
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/cpuid.hxx>
+
+namespace
+{
+class CpuRuntimeDetection_SSE2 : public CppUnit::TestFixture
+{
+public:
+ void testCpuRuntimeDetection();
+
+ CPPUNIT_TEST_SUITE(CpuRuntimeDetection_SSE2);
+ CPPUNIT_TEST(testCpuRuntimeDetection);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void CpuRuntimeDetection_SSE2::testCpuRuntimeDetection()
+{
+ // can only run this function if CPU supports SSE2
+ if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::SSE2))
+ CpuRuntimeDetectionX86Checks::checkSSE2();
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(CpuRuntimeDetection_SSE2);
+
+} // end anonymous namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_SSE2_check.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_SSE2_check.cxx
new file mode 100644
index 0000000000..12e39eabfd
--- /dev/null
+++ b/tools/qa/cppunit/test_cpu_runtime_detection_SSE2_check.cxx
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <tools/simdsupport.hxx>
+
+#include <stdlib.h>
+
+#include "test_cpu_runtime_detection_x86_checks.hxx"
+
+/* WARNING: This file is compiled with SSE2 support, don't call
+ * any function without checking cpuid to check the CPU can actually
+ * handle it, and don't include any headers that contain templates
+ * or inline functions, which includes cppunit.
+ */
+#ifdef LO_SSE2_AVAILABLE
+#define CPPUNIT_ASSERT_EQUAL(a, b) ((a) == (b) ? (void)0 : abort())
+#endif
+void CpuRuntimeDetectionX86Checks::checkSSE2()
+{
+#ifdef LO_SSE2_AVAILABLE
+ // Try some SSE2 intrinsics calculation
+ __m128i a = _mm_set1_epi32(15);
+ __m128i b = _mm_set1_epi32(15);
+ __m128i c = _mm_xor_si128(a, b);
+
+ // Check zero
+ CPPUNIT_ASSERT_EQUAL(0xFFFF, _mm_movemask_epi8(_mm_cmpeq_epi32(c, _mm_setzero_si128())));
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3.cxx
new file mode 100644
index 0000000000..3c0e9d2524
--- /dev/null
+++ b/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3.cxx
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "test_cpu_runtime_detection_x86_checks.hxx"
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/cpuid.hxx>
+
+namespace
+{
+class CpuRuntimeDetection_SSSE3 : public CppUnit::TestFixture
+{
+public:
+ void testCpuRuntimeDetection();
+
+ CPPUNIT_TEST_SUITE(CpuRuntimeDetection_SSSE3);
+ CPPUNIT_TEST(testCpuRuntimeDetection);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void CpuRuntimeDetection_SSSE3::testCpuRuntimeDetection()
+{
+ // can only run this function if CPU supports SSSE3
+ if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::SSSE3))
+ CpuRuntimeDetectionX86Checks::checkSSSE3();
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(CpuRuntimeDetection_SSSE3);
+
+} // end anonymous namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3_check.cxx b/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3_check.cxx
new file mode 100644
index 0000000000..705e5c2ab3
--- /dev/null
+++ b/tools/qa/cppunit/test_cpu_runtime_detection_SSSE3_check.cxx
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <tools/simdsupport.hxx>
+
+#include <stdlib.h>
+
+#include "test_cpu_runtime_detection_x86_checks.hxx"
+
+/* WARNING: This file is compiled with SSSE3 support, don't call
+ * any function without checking cpuid to check the CPU can actually
+ * handle it, and don't include any headers that contain templates
+ * or inline functions, which includes cppunit.
+ */
+#ifdef LO_SSSE3_AVAILABLE
+#define CPPUNIT_ASSERT_EQUAL(a, b) ((a) == (b) ? (void)0 : abort())
+#endif
+void CpuRuntimeDetectionX86Checks::checkSSSE3()
+{
+#ifdef LO_SSSE3_AVAILABLE
+ // Try some SSSE3 intrinsics calculation
+ __m128i a = _mm_set1_epi32(3);
+ __m128i b = _mm_set1_epi32(3);
+ __m128i c = _mm_maddubs_epi16(a, b);
+
+ // Check result is 9
+ CPPUNIT_ASSERT_EQUAL(0xFFFF, _mm_movemask_epi8(_mm_cmpeq_epi32(c, _mm_set1_epi32(9))));
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_cpu_runtime_detection_x86_checks.hxx b/tools/qa/cppunit/test_cpu_runtime_detection_x86_checks.hxx
new file mode 100644
index 0000000000..0046985acd
--- /dev/null
+++ b/tools/qa/cppunit/test_cpu_runtime_detection_x86_checks.hxx
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef INCLUDE_TOOLS_QA_CPPUNIT_TEST_CPU_RUNTIME_DETECTION_X86_CHECKS_H
+#define INCLUDE_TOOLS_QA_CPPUNIT_TEST_CPU_RUNTIME_DETECTION_X86_CHECKS_H
+
+class CpuRuntimeDetectionX86Checks
+{
+public:
+ static void checkAVX2();
+ static void checkSSE2();
+ static void checkSSSE3();
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_cpuid.cxx b/tools/qa/cppunit/test_cpuid.cxx
new file mode 100644
index 0000000000..7d9b5c41cc
--- /dev/null
+++ b/tools/qa/cppunit/test_cpuid.cxx
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <tools/cpuid.hxx>
+#include <rtl/ustring.hxx>
+
+namespace
+{
+class CpuInstructionSetSupport : public CppUnit::TestFixture
+{
+public:
+ void testCpuInstructionSetSupport();
+
+ CPPUNIT_TEST_SUITE(CpuInstructionSetSupport);
+ CPPUNIT_TEST(testCpuInstructionSetSupport);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void CpuInstructionSetSupport::testCpuInstructionSetSupport()
+{
+ OUString aString = cpuid::instructionSetSupportedString();
+
+ if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::SSE2))
+ {
+ CPPUNIT_ASSERT(aString.indexOf("SSE2") >= 0);
+ }
+
+ if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::SSSE3))
+ {
+ CPPUNIT_ASSERT(aString.indexOf("SSSE3") >= 0);
+ }
+
+ if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::AVX))
+ {
+ CPPUNIT_ASSERT(aString.indexOf("AVX") > 0);
+ }
+
+ if (cpuid::isCpuInstructionSetSupported(cpuid::InstructionSetFlags::AVX2))
+ {
+ CPPUNIT_ASSERT(aString.indexOf("AVX2") > 0);
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(CpuInstructionSetSupport);
+
+} // end anonymous namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_date.cxx b/tools/qa/cppunit/test_date.cxx
new file mode 100644
index 0000000000..e11270e6a2
--- /dev/null
+++ b/tools/qa/cppunit/test_date.cxx
@@ -0,0 +1,564 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/date.hxx>
+
+namespace tools
+{
+class DateTest : public CppUnit::TestFixture
+{
+public:
+ void testDate();
+ void testLeapYear();
+ void testGetDaysInYear();
+ void testValidGregorianDate();
+ void testValidDate();
+ void testNormalize();
+ void testGetDayOfWeek();
+ void testGetDaysInMonth();
+ void testIsBetween();
+ void testIsEndOfMonth();
+
+ CPPUNIT_TEST_SUITE(DateTest);
+ CPPUNIT_TEST(testDate);
+ CPPUNIT_TEST(testLeapYear);
+ CPPUNIT_TEST(testGetDaysInYear);
+ CPPUNIT_TEST(testValidGregorianDate);
+ CPPUNIT_TEST(testValidDate);
+ CPPUNIT_TEST(testNormalize);
+ CPPUNIT_TEST(testGetDayOfWeek);
+ CPPUNIT_TEST(testGetDaysInMonth);
+ CPPUNIT_TEST(testIsBetween);
+ CPPUNIT_TEST(testIsEndOfMonth);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void DateTest::testDate()
+{
+ const Date aCE(1, 1, 1); // first day CE
+ const Date aBCE(31, 12, -1); // last day BCE
+ const Date aMin(1, 1, -32768); // minimum date
+ const Date aMax(31, 12, 32767); // maximum date
+ Date aDate(Date::EMPTY);
+ const sal_Int32 kMinDays = -11968265;
+ const sal_Int32 kMaxDays = 11967900;
+
+ // Last day BCE to first day CE is 1 day difference.
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aCE - aBCE);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), aBCE - aCE);
+ aDate = aBCE;
+ aDate.AddDays(1);
+ CPPUNIT_ASSERT_EQUAL(aCE.GetDate(), aDate.GetDate());
+ aDate = aCE;
+ aDate.AddDays(-1);
+ CPPUNIT_ASSERT_EQUAL(aBCE.GetDate(), aDate.GetDate());
+
+ // The entire BCE and CE ranges cover that many days. Day 0 is -0001-12-31
+ CPPUNIT_ASSERT_EQUAL(kMaxDays, aMax - aBCE);
+ CPPUNIT_ASSERT_EQUAL(kMinDays, aMin - aBCE);
+
+ // Truncate at limits, not under-/overflow or wrap.
+ aDate = aMin;
+ aDate.AddDays(-1);
+ CPPUNIT_ASSERT_EQUAL(aMin.GetDate(), aDate.GetDate());
+ aDate = aMax;
+ aDate.AddDays(1);
+ CPPUNIT_ASSERT_EQUAL(aMax.GetDate(), aDate.GetDate());
+ aDate = aBCE;
+ aDate.AddDays(kMinDays - 10);
+ CPPUNIT_ASSERT_EQUAL(aMin.GetDate(), aDate.GetDate());
+ aDate = aBCE;
+ aDate.AddDays(kMaxDays + 10);
+ CPPUNIT_ASSERT_EQUAL(aMax.GetDate(), aDate.GetDate());
+ aDate = aMax;
+ aDate.SetDay(32);
+ aDate.Normalize();
+ CPPUNIT_ASSERT_EQUAL(aMax.GetDate(), aDate.GetDate());
+ CPPUNIT_ASSERT(!aDate.IsEmpty());
+
+ // 0001-00-x normalized to -0001-12-x
+ aDate.SetYear(1);
+ aDate.SetMonth(0);
+ aDate.SetDay(22);
+ aDate.Normalize();
+ CPPUNIT_ASSERT_EQUAL(Date(22, 12, -1).GetDate(), aDate.GetDate());
+
+ sal_uInt32 nExpected = 11222;
+ CPPUNIT_ASSERT_EQUAL(nExpected, aDate.GetDateUnsigned());
+ // 1999-02-32 normalized to 1999-03-04
+ aDate.SetYear(1999);
+ aDate.SetMonth(2);
+ aDate.SetDay(32);
+ aDate.Normalize();
+ CPPUNIT_ASSERT_EQUAL(Date(4, 3, 1999).GetDate(), aDate.GetDate());
+
+ // Empty date is not normalized and stays empty date.
+ aDate = Date(Date::EMPTY);
+ aDate.Normalize();
+ CPPUNIT_ASSERT_EQUAL(Date(Date::EMPTY).GetDate(), aDate.GetDate());
+ CPPUNIT_ASSERT(!aDate.IsValidDate()); // GetDate() also shall have no normalizing side effect
+
+ // 0000-01-00 normalized to -0001-12-31
+ // SetYear(0) asserts, use empty date to force.
+ aDate = Date(Date::EMPTY);
+ aDate.SetMonth(1);
+ aDate.SetDay(0);
+ aDate.Normalize();
+ CPPUNIT_ASSERT_EQUAL(Date(31, 12, -1).GetDate(), aDate.GetDate());
+
+ // 1999-00-00 normalized to 1998-12-31 (not 1998-11-30, or otherwise
+ // also 0001-00-00 should be -0001-11-30 which it should not, should it?)
+ aDate.SetYear(1999);
+ aDate.SetMonth(0);
+ aDate.SetDay(0);
+ aDate.Normalize();
+ CPPUNIT_ASSERT_EQUAL(Date(31, 12, 1998).GetDate(), aDate.GetDate());
+
+ // 0001-00-00 normalized to -0001-12-31
+ aDate.SetYear(1);
+ aDate.SetMonth(0);
+ aDate.SetDay(0);
+ aDate.Normalize();
+ CPPUNIT_ASSERT_EQUAL(Date(31, 12, -1).GetDate(), aDate.GetDate());
+}
+
+void DateTest::testLeapYear()
+{
+ {
+ Date aDate(1, 1, 2000);
+ CPPUNIT_ASSERT(aDate.IsLeapYear());
+ }
+
+ {
+ Date aDate(1, 1, 1900);
+ CPPUNIT_ASSERT(!aDate.IsLeapYear());
+ }
+
+ {
+ Date aDate(1, 1, 1999);
+ CPPUNIT_ASSERT(!aDate.IsLeapYear());
+ }
+
+ {
+ Date aDate(1, 1, 2004);
+ CPPUNIT_ASSERT(aDate.IsLeapYear());
+ }
+
+ {
+ Date aDate(1, 1, 400);
+ CPPUNIT_ASSERT(aDate.IsLeapYear());
+ }
+
+ {
+ // Year -1 is a leap year.
+ Date aDate(28, 2, -1);
+ aDate.AddDays(1);
+ CPPUNIT_ASSERT(aDate.IsLeapYear());
+ CPPUNIT_ASSERT_EQUAL(Date(29, 2, -1).GetDate(), aDate.GetDate());
+ }
+
+ {
+ Date aDate(1, 3, -1);
+ aDate.AddDays(-1);
+ CPPUNIT_ASSERT(aDate.IsLeapYear());
+ CPPUNIT_ASSERT_EQUAL(Date(29, 2, -1).GetDate(), aDate.GetDate());
+ }
+
+ {
+ // Year -5 is a leap year.
+ Date aDate(28, 2, -5);
+ aDate.AddDays(1);
+ CPPUNIT_ASSERT(aDate.IsLeapYear());
+ CPPUNIT_ASSERT_EQUAL(Date(29, 2, -5).GetDate(), aDate.GetDate());
+ }
+
+ {
+ Date aDate(1, 3, -5);
+ aDate.AddDays(-1);
+ CPPUNIT_ASSERT(aDate.IsLeapYear());
+ CPPUNIT_ASSERT_EQUAL(Date(29, 2, -5).GetDate(), aDate.GetDate());
+ }
+}
+
+void DateTest::testGetDaysInYear()
+{
+ {
+ Date aDate(1, 1, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(366), aDate.GetDaysInYear());
+ }
+
+ {
+ Date aDate(1, 1, 1900);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(365), aDate.GetDaysInYear());
+ }
+
+ {
+ Date aDate(1, 1, 1999);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(365), aDate.GetDaysInYear());
+ }
+
+ {
+ Date aDate(1, 1, 2004);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(366), aDate.GetDaysInYear());
+ }
+
+ {
+ Date aDate(1, 1, 400);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(366), aDate.GetDaysInYear());
+ }
+}
+
+void DateTest::testValidGregorianDate()
+{
+ {
+ Date aDate(1, 0, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(1, 13, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(1, 1, 1581);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(1, 9, 1582);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(1, 10, 1582);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(32, 1, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(29, 2, 2000);
+ CPPUNIT_ASSERT(aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(29, 2, 2001);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(32, 3, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(31, 4, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(32, 5, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(31, 6, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(32, 7, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(32, 8, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(31, 9, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(32, 10, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(31, 11, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+
+ {
+ Date aDate(32, 12, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidAndGregorian());
+ }
+}
+
+void DateTest::testValidDate()
+{
+ {
+ // Empty date is not a valid date.
+ Date aDate(Date::EMPTY);
+ CPPUNIT_ASSERT(aDate.IsEmpty());
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(32, 1, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(29, 2, 2000);
+ CPPUNIT_ASSERT(aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(29, 2, 2001);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(32, 3, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(31, 4, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(32, 5, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(31, 6, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(32, 7, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(32, 8, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(31, 9, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(32, 10, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(31, 11, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+
+ {
+ Date aDate(32, 12, 2000);
+ CPPUNIT_ASSERT(!aDate.IsValidDate());
+ }
+}
+
+void DateTest::testNormalize()
+{
+ {
+ Date aDate(32, 2, 1999);
+ aDate.Normalize();
+ Date aExpectedDate(4, 3, 1999);
+ CPPUNIT_ASSERT_EQUAL(aExpectedDate, aDate);
+ }
+
+ {
+ Date aDate(1, 13, 1999);
+ aDate.Normalize();
+ Date aExpectedDate(1, 1, 2000);
+ CPPUNIT_ASSERT_EQUAL(aExpectedDate, aDate);
+ }
+
+ {
+ Date aDate(42, 13, 1999);
+ aDate.Normalize();
+ Date aExpectedDate(11, 2, 2000);
+ CPPUNIT_ASSERT_EQUAL(aExpectedDate, aDate);
+ }
+
+ {
+ Date aDate(1, 0, 1);
+ aDate.Normalize();
+ Date aExpectedDate(1, 12, -1);
+ CPPUNIT_ASSERT_EQUAL(aExpectedDate, aDate);
+ }
+}
+
+void DateTest::testGetDayOfWeek()
+{
+ {
+ DayOfWeek eExpectedDay = DayOfWeek::MONDAY;
+ Date aDate(30, 4, 2018);
+ CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek());
+ }
+
+ {
+ DayOfWeek eExpectedDay = DayOfWeek::TUESDAY;
+ Date aDate(1, 5, 2018);
+ CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek());
+ }
+
+ {
+ DayOfWeek eExpectedDay = DayOfWeek::WEDNESDAY;
+ Date aDate(2, 5, 2018);
+ CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek());
+ }
+
+ {
+ DayOfWeek eExpectedDay = DayOfWeek::THURSDAY;
+ Date aDate(3, 5, 2018);
+ CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek());
+ }
+
+ {
+ DayOfWeek eExpectedDay = DayOfWeek::FRIDAY;
+ Date aDate(4, 5, 2018);
+ CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek());
+ }
+
+ {
+ DayOfWeek eExpectedDay = DayOfWeek::SATURDAY;
+ Date aDate(5, 5, 2018);
+ CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek());
+ }
+
+ {
+ DayOfWeek eExpectedDay = DayOfWeek::SUNDAY;
+ Date aDate(6, 5, 2018);
+ CPPUNIT_ASSERT_EQUAL(eExpectedDay, aDate.GetDayOfWeek());
+ }
+}
+
+void DateTest::testGetDaysInMonth()
+{
+ {
+ Date aDate(1, 1, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 2, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(29), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 2, 1999);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(28), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 3, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 4, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 5, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 6, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 7, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 8, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 9, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 10, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 11, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(30), aDate.GetDaysInMonth());
+ }
+
+ {
+ Date aDate(1, 12, 2000);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt16(31), aDate.GetDaysInMonth());
+ }
+}
+
+void DateTest::testIsBetween()
+{
+ Date aDate(6, 4, 2018);
+ CPPUNIT_ASSERT(aDate.IsBetween(Date(1, 1, 2018), Date(1, 12, 2018)));
+}
+
+void DateTest::testIsEndOfMonth()
+{
+ {
+ Date aDate(31, 12, 2000);
+ CPPUNIT_ASSERT(aDate.IsEndOfMonth());
+ }
+
+ {
+ Date aDate(30, 12, 2000);
+ CPPUNIT_ASSERT(!aDate.IsEndOfMonth());
+ }
+
+ {
+ Date aDate(29, 2, 2000);
+ CPPUNIT_ASSERT(aDate.IsEndOfMonth());
+ }
+
+ {
+ Date aDate(28, 2, 2000);
+ CPPUNIT_ASSERT(!aDate.IsEndOfMonth());
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DateTest);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/qa/cppunit/test_duration.cxx b/tools/qa/cppunit/test_duration.cxx
new file mode 100644
index 0000000000..c4032be83a
--- /dev/null
+++ b/tools/qa/cppunit/test_duration.cxx
@@ -0,0 +1,356 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/duration.hxx>
+#include <tools/datetime.hxx>
+
+namespace tools
+{
+class DurationTest : public CppUnit::TestFixture
+{
+public:
+ void testDuration();
+
+ CPPUNIT_TEST_SUITE(DurationTest);
+ CPPUNIT_TEST(testDuration);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void DurationTest::testDuration()
+{
+ {
+ const Duration aD(Time(0), Time(12, 0, 0));
+ CPPUNIT_ASSERT_EQUAL(0.5, aD.GetInDays());
+ }
+ {
+ const Duration aD(Time(24, 0, 0), Time(12, 0, 0));
+ CPPUNIT_ASSERT_EQUAL(-0.5, aD.GetInDays());
+ }
+ {
+ const DateTime aS(Date(23, 11, 1999), Time(6, 0, 0));
+ const DateTime aE(Date(24, 11, 1999), Time(18, 0, 0));
+ const Duration aD(aS, aE);
+ CPPUNIT_ASSERT_EQUAL(1.5, aD.GetInDays());
+ DateTime aDT1(aS);
+ const DateTime aDT2 = aDT1 + aD;
+ CPPUNIT_ASSERT_EQUAL(aE, aDT2);
+ aDT1 += aD;
+ CPPUNIT_ASSERT_EQUAL(aE, aDT1);
+ aDT1 += aD;
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(26, 11, 1999), Time(6, 0, 0)), aDT1);
+ }
+ {
+ const DateTime aS(Date(23, 11, 1999), Time(18, 0, 0));
+ const DateTime aE(Date(24, 11, 1999), Time(6, 0, 0));
+ const Duration aD(aS, aE);
+ CPPUNIT_ASSERT_EQUAL(0.5, aD.GetInDays());
+ DateTime aDT1(aS);
+ const DateTime aDT2 = aDT1 + aD;
+ CPPUNIT_ASSERT_EQUAL(aE, aDT2);
+ aDT1 += aD;
+ CPPUNIT_ASSERT_EQUAL(aE, aDT1);
+ aDT1 += aD;
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(18, 0, 0)), aDT1);
+ }
+ {
+ const DateTime aS(Date(24, 11, 1999), Time(18, 0, 0));
+ const DateTime aE(Date(23, 11, 1999), Time(6, 0, 0));
+ const Duration aD(aS, aE);
+ CPPUNIT_ASSERT_EQUAL(-1.5, aD.GetInDays());
+ DateTime aDT1(aS);
+ const DateTime aDT2 = aDT1 + aD;
+ CPPUNIT_ASSERT_EQUAL(aE, aDT2);
+ aDT1 += aD;
+ CPPUNIT_ASSERT_EQUAL(aE, aDT1);
+ aDT1 += aD;
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(21, 11, 1999), Time(18, 0, 0)), aDT1);
+ }
+ {
+ const DateTime aS(Date(24, 11, 1999), Time(6, 0, 0));
+ const DateTime aE(Date(23, 11, 1999), Time(18, 0, 0));
+ const Duration aD(aS, aE);
+ CPPUNIT_ASSERT_EQUAL(-0.5, aD.GetInDays());
+ DateTime aDT1(aS);
+ const DateTime aDT2 = aDT1 + aD;
+ CPPUNIT_ASSERT_EQUAL(aE, aDT2);
+ aDT1 += aD;
+ CPPUNIT_ASSERT_EQUAL(aE, aDT1);
+ aDT1 += aD;
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(23, 11, 1999), Time(6, 0, 0)), aDT1);
+ }
+ {
+ const Duration aD(1.5);
+ CPPUNIT_ASSERT_EQUAL(1.5, aD.GetInDays());
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(18, 0, 0)),
+ DateTime(Date(23, 11, 1999), Time(6, 0, 0)) + aD);
+ }
+ {
+ const Duration aD(-1.5);
+ CPPUNIT_ASSERT_EQUAL(-1.5, aD.GetInDays());
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(23, 11, 1999), Time(6, 0, 0)),
+ DateTime(Date(24, 11, 1999), Time(18, 0, 0)) + aD);
+ }
+ {
+ const Duration aD(-1.5);
+ const Duration aN = -aD;
+ CPPUNIT_ASSERT_EQUAL(1.5, aN.GetInDays());
+ }
+ {
+ const Duration aD(1, Time(2, 3, 4, 5));
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aD.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(2), aD.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(3), aD.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(4), aD.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(5), aD.GetTime().GetNanoSec());
+ }
+ {
+ // 235929599 seconds == SAL_MAX_UINT16 hours + 59 minutes + 59 seconds
+ const Duration aD(0, Time(0, 0, 235929599, Time::nanoSecPerSec - 1));
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2730), aD.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(15), aD.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aD.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aD.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(999999999), aD.GetTime().GetNanoSec());
+ }
+ {
+ // 235929599 seconds == SAL_MAX_UINT16 hours + 59 minutes + 59 seconds
+ const Duration aD(0, 0, 0, 235929599, Time::nanoSecPerSec - 1);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2730), aD.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(15), aD.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aD.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aD.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(999999999), aD.GetTime().GetNanoSec());
+ }
+ {
+ const Duration aD(1, 2, 3, 4, 5);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aD.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(2), aD.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(3), aD.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(4), aD.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(5), aD.GetTime().GetNanoSec());
+ }
+ {
+ const Duration aD(-1, 2, 3, 4, 5);
+ CPPUNIT_ASSERT(aD.IsNegative());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), aD.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(2), aD.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(3), aD.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(4), aD.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(5), aD.GetTime().GetNanoSec());
+ }
+ {
+ const Duration aD(1, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT64);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(182202802), aD.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(17), aD.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(48), aD.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(709551615), aD.GetTime().GetNanoSec());
+ }
+ {
+ const Duration aD(-1, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT64);
+ CPPUNIT_ASSERT(aD.IsNegative());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-182202802), aD.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(17), aD.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(48), aD.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(709551615), aD.GetTime().GetNanoSec());
+ }
+ { // Maximum days with all max possible.
+ const Duration aD(1965280846, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT32,
+ SAL_MAX_UINT64);
+ CPPUNIT_ASSERT_EQUAL(SAL_MAX_INT32, aD.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(17), aD.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(48), aD.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(709551615), aD.GetTime().GetNanoSec());
+ }
+ { // Maximum negative days with all max possible.
+ const Duration aD(-1965280847, SAL_MAX_UINT32, SAL_MAX_UINT32, SAL_MAX_UINT32,
+ SAL_MAX_UINT64);
+ CPPUNIT_ASSERT_EQUAL(SAL_MIN_INT32, aD.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(17), aD.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(48), aD.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(709551615), aD.GetTime().GetNanoSec());
+ }
+ { // Add()
+ const DateTime aS(Date(23, 11, 1999), Time(0, 0, 0));
+ const DateTime aE(Date(23, 11, 1999), Time(1, 23, 45));
+ const Duration aD(aS, aE);
+ Duration aV = aD;
+ bool bOverflow = true;
+ aV.Add(aD, bOverflow);
+ CPPUNIT_ASSERT(!bOverflow);
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(23, 11, 1999), Time(2, 47, 30)),
+ DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
+ for (int i = 0; i < 20; ++i)
+ aV.Add(aD, bOverflow);
+ CPPUNIT_ASSERT(!bOverflow);
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(6, 42, 30)),
+ DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aV.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec());
+ CPPUNIT_ASSERT(aV.GetTime().GetTime() > 0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.27951388888889, aV.GetInDays(), 1E-14);
+ // Negative duration.
+ const Duration aN(aE, aS);
+ aV = aN;
+ aV.Add(aN, bOverflow);
+ CPPUNIT_ASSERT(!bOverflow);
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(22, 11, 1999), Time(21, 12, 30)),
+ DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
+ for (int i = 0; i < 20; ++i)
+ aV.Add(aN, bOverflow);
+ CPPUNIT_ASSERT(!bOverflow);
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(21, 11, 1999), Time(17, 17, 30)),
+ DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), aV.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec());
+ CPPUNIT_ASSERT(aV.GetTime().GetTime() < 0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.27951388888889, aV.GetInDays(), 1E-14);
+ }
+ { // Mult()
+ const DateTime aS(Date(23, 11, 1999), Time(0, 0, 0));
+ const DateTime aE(Date(23, 11, 1999), Time(1, 23, 45));
+ const Duration aD(aS, aE);
+ bool bOverflow = true;
+ Duration aV = aD.Mult(22, bOverflow);
+ CPPUNIT_ASSERT(!bOverflow);
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(24, 11, 1999), Time(6, 42, 30)),
+ DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aV.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec());
+ CPPUNIT_ASSERT(aV.GetTime().GetTime() > 0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.27951388888889, aV.GetInDays(), 1E-14);
+ // Negative duration.
+ const Duration aN(aE, aS);
+ bOverflow = true;
+ aV = aN.Mult(22, bOverflow);
+ CPPUNIT_ASSERT(!bOverflow);
+ CPPUNIT_ASSERT_EQUAL(DateTime(Date(21, 11, 1999), Time(17, 17, 30)),
+ DateTime(Date(23, 11, 1999), Time(0, 0, 0)) + aV);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-1), aV.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(6), aV.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(42), aV.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(30), aV.GetTime().GetSec());
+ CPPUNIT_ASSERT(aV.GetTime().GetTime() < 0);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.27951388888889, aV.GetInDays(), 1E-14);
+ }
+ { // Mult() including days.
+ const Duration aD(1.5);
+ bool bOverflow = true;
+ Duration aV = aD.Mult(10, bOverflow);
+ CPPUNIT_ASSERT(!bOverflow);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(15.0, aV.GetInDays(), 0.0);
+ }
+ { // Mult() including days.
+ const Duration aD(-1.5);
+ bool bOverflow = true;
+ Duration aV = aD.Mult(10, bOverflow);
+ CPPUNIT_ASSERT(!bOverflow);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-15.0, aV.GetInDays(), 0.0);
+ }
+ { // Mult() including days.
+ const Duration aD(1.5);
+ bool bOverflow = true;
+ Duration aV = aD.Mult(-10, bOverflow);
+ CPPUNIT_ASSERT(!bOverflow);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-15.0, aV.GetInDays(), 0.0);
+ }
+ { // Mult() including days.
+ const Duration aD(-1.5);
+ bool bOverflow = true;
+ Duration aV = aD.Mult(-10, bOverflow);
+ CPPUNIT_ASSERT(!bOverflow);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(15.0, aV.GetInDays(), 0.0);
+ }
+ { // Mult() with overflow.
+ const Duration aD(SAL_MAX_INT32);
+ bool bOverflow = false;
+ Duration aV = aD.Mult(2, bOverflow);
+ CPPUNIT_ASSERT(bOverflow);
+ CPPUNIT_ASSERT_EQUAL(SAL_MAX_INT32, aV.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1),
+ aV.GetTime().GetNanoSec());
+ }
+ { // Mult() with overflow.
+ const Duration aD(SAL_MIN_INT32);
+ bool bOverflow = false;
+ Duration aV = aD.Mult(2, bOverflow);
+ CPPUNIT_ASSERT(bOverflow);
+ CPPUNIT_ASSERT_EQUAL(SAL_MIN_INT32, aV.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1),
+ aV.GetTime().GetNanoSec());
+ }
+ { // Mult() with overflow.
+ const Duration aD(SAL_MAX_INT32);
+ bool bOverflow = false;
+ Duration aV = aD.Mult(-2, bOverflow);
+ CPPUNIT_ASSERT(bOverflow);
+ CPPUNIT_ASSERT_EQUAL(SAL_MIN_INT32, aV.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1),
+ aV.GetTime().GetNanoSec());
+ }
+ { // Mult() with overflow.
+ const Duration aD(SAL_MIN_INT32);
+ bool bOverflow = false;
+ Duration aV = aD.Mult(-2, bOverflow);
+ CPPUNIT_ASSERT(bOverflow);
+ CPPUNIT_ASSERT_EQUAL(SAL_MAX_INT32, aV.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(23), aV.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(59), aV.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(Time::nanoSecPerSec - 1),
+ aV.GetTime().GetNanoSec());
+ }
+ { // Inaccurate double yielding exact duration.
+ const Time aS(15, 0, 0);
+ const Time aE(16, 0, 0);
+ const Duration aD(aE.GetTimeInDays() - aS.GetTimeInDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), aD.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0), aD.GetTime().GetNanoSec());
+ }
+ { // Inaccurate double yielding exact duration, negative.
+ const Time aS(15, 0, 0);
+ const Time aE(16, 0, 0);
+ const Duration aD(aS.GetTimeInDays() - aE.GetTimeInDays());
+ CPPUNIT_ASSERT(aD.IsNegative());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), aD.GetDays());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), aD.GetTime().GetHour());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetMin());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0), aD.GetTime().GetSec());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0), aD.GetTime().GetNanoSec());
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DurationTest);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/qa/cppunit/test_fract.cxx b/tools/qa/cppunit/test_fract.cxx
new file mode 100644
index 0000000000..1c3b8ad029
--- /dev/null
+++ b/tools/qa/cppunit/test_fract.cxx
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/types.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <rtl/math.hxx>
+#include <tools/fract.hxx>
+
+namespace tools
+{
+
+class FractionTest : public CppUnit::TestFixture
+{
+public:
+
+ void testFraction()
+ {
+ const Fraction aFract(1082130431,1073741824);
+ CPPUNIT_ASSERT_MESSAGE( "Fraction #1 not approximately equal to 1.007812499068677",
+ rtl::math::approxEqual(static_cast<double>(aFract),1.007812499068677) );
+
+ Fraction aFract2( aFract );
+ aFract2.ReduceInaccurate(8);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #2 not 1",
+ sal_Int32(1), aFract2.GetNumerator() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #2 not 1",
+ sal_Int32(1), aFract2.GetDenominator() );
+
+ Fraction aFract3( 0x7AAAAAAA, 0x35555555 );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 cancellation wrong",
+ sal_Int32(0x7AAAAAAA), aFract3.GetNumerator() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 cancellation wrong",
+ sal_Int32(0x35555555), aFract3.GetDenominator() );
+ aFract3.ReduceInaccurate(30);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 ReduceInaccurate erroneously cut precision",
+ sal_Int32(0x7AAAAAAA), aFract3.GetNumerator() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 ReduceInaccurate erroneously cut precision",
+ sal_Int32(0x35555555), aFract3.GetDenominator() );
+
+ aFract3.ReduceInaccurate(29);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 29 bits failed",
+ sal_Int32(0x3D555555), aFract3.GetNumerator() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 29 bits failed",
+ sal_Int32(0x1AAAAAAA), aFract3.GetDenominator() );
+
+ aFract3.ReduceInaccurate(9);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 9 bits failed",
+ sal_Int32(0x0147), aFract3.GetNumerator() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 9 bits failed",
+ sal_Int32(0x008E), aFract3.GetDenominator() );
+
+ aFract3.ReduceInaccurate(1);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 1 bit failed",
+ sal_Int32(2), aFract3.GetNumerator() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 1 bit failed",
+ sal_Int32(1), aFract3.GetDenominator() );
+
+ aFract3.ReduceInaccurate(0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 0 bits failed",
+ sal_Int32(2), aFract3.GetNumerator() );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Fraction #3 reduce to 0 bits failed",
+ sal_Int32(1), aFract3.GetDenominator() );
+
+ }
+
+ void testMinLongDouble() {
+ Fraction f(double(SAL_MIN_INT32));
+ CPPUNIT_ASSERT_EQUAL(SAL_MIN_INT32, f.GetNumerator());
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1), f.GetDenominator());
+ }
+
+ void testCreateFromDoubleIn32BitsPlatform() {
+ // This pass in 64 bits but fail in 32 bits
+ Fraction f(0.960945);
+ CPPUNIT_ASSERT_EQUAL(true, f.IsValid());
+ }
+
+ CPPUNIT_TEST_SUITE(FractionTest);
+ CPPUNIT_TEST(testFraction);
+ CPPUNIT_TEST(testMinLongDouble);
+ CPPUNIT_TEST(testCreateFromDoubleIn32BitsPlatform);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION(FractionTest);
+} // namespace tools
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_fround.cxx b/tools/qa/cppunit/test_fround.cxx
new file mode 100644
index 0000000000..4014ff3cb4
--- /dev/null
+++ b/tools/qa/cppunit/test_fround.cxx
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/types.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/helpers.hxx>
+
+namespace tools
+{
+class FRoundTest : public CppUnit::TestFixture
+{
+public:
+ void testPositiveFRound()
+ {
+ sal_Int64 nExpected = 2;
+ sal_Int64 nActual = FRound(1.6);
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+
+ nExpected = 1;
+ nActual = FRound(1.4);
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+ }
+
+ void testNegativeFRound()
+ {
+ sal_Int64 nExpected = -2;
+ sal_Int64 nActual = FRound(-1.6);
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+
+ nExpected = -1;
+ nActual = FRound(-1.4);
+ CPPUNIT_ASSERT_EQUAL(nExpected, nActual);
+ }
+
+ CPPUNIT_TEST_SUITE(FRoundTest);
+ CPPUNIT_TEST(testPositiveFRound);
+ CPPUNIT_TEST(testNegativeFRound);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(FRoundTest);
+} // namespace tools
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_guid.cxx b/tools/qa/cppunit/test_guid.cxx
new file mode 100644
index 0000000000..f16eb38931
--- /dev/null
+++ b/tools/qa/cppunit/test_guid.cxx
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/types.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <tools/Guid.hxx>
+
+namespace tools
+{
+class GuidTest : public CppUnit::TestFixture
+{
+public:
+ void testGetString()
+ {
+ sal_uInt8 pArray[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+ Guid aGuid(pArray);
+ CPPUNIT_ASSERT_EQUAL("{01020304-0506-0708-090A-0B0C0D0E0F10}"_ostr, aGuid.getString());
+ }
+
+ void testCreate()
+ {
+ // data is generated when Guid is created
+ Guid aGuid1(Guid::Generate);
+
+ // check it's not initialized to 0
+ CPPUNIT_ASSERT(*std::max_element(aGuid1.begin(), aGuid1.end()) > 0u);
+
+ // data is generated when Guid is created
+ Guid aGuid2(Guid::Generate);
+
+ CPPUNIT_ASSERT_EQUAL(aGuid1, aGuid1);
+ CPPUNIT_ASSERT_EQUAL(aGuid2, aGuid2);
+
+ CPPUNIT_ASSERT(aGuid1 != aGuid2);
+ }
+
+ void testParse()
+ {
+ sal_uInt8 pArray1[16] = { 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5, 5, 5, 5 };
+ Guid aGuid1(pArray1);
+
+ Guid aGuid2("{01010101-0202-0303-0404-050505050505}");
+ CPPUNIT_ASSERT_EQUAL(aGuid1, aGuid2);
+
+ sal_uInt8 pArray2[16] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+ Guid aGuid3(pArray2);
+
+ Guid aGuid4("{FFffFFff-FFff-FFff-FFff-FFffFFffFFff}");
+ CPPUNIT_ASSERT_EQUAL(aGuid3, aGuid4);
+
+ Guid aGuid5("{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}");
+ CPPUNIT_ASSERT_EQUAL(aGuid5, aGuid4);
+
+ Guid aGuid6("01010101-0202-0303-0404-0505050505005");
+ CPPUNIT_ASSERT(aGuid6.isEmpty());
+
+ Guid aGuid7("Random");
+ CPPUNIT_ASSERT(aGuid7.isEmpty());
+
+ Guid aGuid8("{0G010101-0202-0303-0404-050505050505}");
+ CPPUNIT_ASSERT(aGuid8.isEmpty());
+
+ Guid aGuid9("{FFAAFFAA-FFAA-FFAA-FFAA-FF00FF11FF22}");
+ CPPUNIT_ASSERT(!aGuid9.isEmpty());
+
+ Guid aGuid10("{FFAAFFAA?FFAA-FFAA-FFAA-FF00FF11FF22}");
+ CPPUNIT_ASSERT(aGuid10.isEmpty());
+ }
+
+ void testEmpty()
+ {
+ sal_uInt8 pArray1[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ Guid aGuid1(pArray1);
+ CPPUNIT_ASSERT(aGuid1.isEmpty());
+
+ sal_uInt8 pArray2[16] = { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ Guid aGuid2(pArray2);
+ CPPUNIT_ASSERT(!aGuid2.isEmpty());
+
+ Guid aGuid3;
+ CPPUNIT_ASSERT(aGuid3.isEmpty());
+ }
+
+ void testCopyAndAssign()
+ {
+ sal_uInt8 pArray1[16] = { 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5, 5, 5, 5 };
+ Guid aGuid1(pArray1);
+
+ // test copy constructor
+ Guid aGuid2(aGuid1);
+ CPPUNIT_ASSERT_EQUAL(aGuid1, aGuid2);
+ CPPUNIT_ASSERT(std::equal(aGuid1.cbegin(), aGuid1.cend(), aGuid2.cbegin(), aGuid2.cend()));
+
+ // test assign
+ Guid aGuid3 = aGuid1;
+ CPPUNIT_ASSERT_EQUAL(aGuid3, aGuid1);
+ CPPUNIT_ASSERT(std::equal(aGuid3.cbegin(), aGuid3.cend(), aGuid1.cbegin(), aGuid1.cend()));
+ CPPUNIT_ASSERT_EQUAL(aGuid3, aGuid2);
+ CPPUNIT_ASSERT(std::equal(aGuid3.cbegin(), aGuid3.cend(), aGuid2.cbegin(), aGuid2.cend()));
+ }
+
+ CPPUNIT_TEST_SUITE(GuidTest);
+ CPPUNIT_TEST(testGetString);
+ CPPUNIT_TEST(testCreate);
+ CPPUNIT_TEST(testParse);
+ CPPUNIT_TEST(testEmpty);
+ CPPUNIT_TEST(testCopyAndAssign);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(GuidTest);
+
+} // namespace tools
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_inetmime.cxx b/tools/qa/cppunit/test_inetmime.cxx
new file mode 100644
index 0000000000..d079f19cf2
--- /dev/null
+++ b/tools/qa/cppunit/test_inetmime.cxx
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <rtl/ustring.hxx>
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/inetmime.hxx>
+
+namespace
+{
+
+ class Test: public CppUnit::TestFixture
+ {
+ bool testDecode(char const * input, char const * expected);
+ public:
+ void test_decodeHeaderFieldBody();
+
+ void test_scanContentType_basic();
+ void test_scanContentType_rfc2231();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(test_decodeHeaderFieldBody);
+ CPPUNIT_TEST(test_scanContentType_basic);
+ CPPUNIT_TEST(test_scanContentType_rfc2231);
+ CPPUNIT_TEST_SUITE_END();
+ };
+
+ bool Test::testDecode(char const * input, char const * expected)
+ {
+ OUString result = INetMIME::decodeHeaderFieldBody(input);
+ return result.equalsAscii(expected);
+ }
+
+ void Test::test_decodeHeaderFieldBody()
+ {
+ CPPUNIT_ASSERT(testDecode("=?iso-8859-1?B?QQ==?=", "A"));
+ CPPUNIT_ASSERT(testDecode("=?iso-8859-1?B?QUI=?=", "AB"));
+ CPPUNIT_ASSERT(testDecode("=?iso-8859-1?B?QUJD?=", "ABC"));
+ }
+
+ void Test::test_scanContentType_basic()
+ {
+ OUString input
+ = "TEST/subTST; parm1=Value1; Parm2=\"unpacked value; %20\"";
+ // Just scan input for valid string:
+ auto end = INetMIME::scanContentType(input);
+ CPPUNIT_ASSERT(end != nullptr);
+ CPPUNIT_ASSERT_EQUAL(OUString(), OUString(end));
+ // Scan input and parse type, subType and parameters:
+ OUString type;
+ OUString subType;
+ INetContentTypeParameterList parameters;
+ end = INetMIME::scanContentType(input,
+ &type, &subType, &parameters);
+ CPPUNIT_ASSERT(end != nullptr);
+ CPPUNIT_ASSERT_EQUAL(OUString(), OUString(end));
+ CPPUNIT_ASSERT_EQUAL(OUString("test"), type);
+ CPPUNIT_ASSERT_EQUAL(OUString("subtst"), subType);
+ CPPUNIT_ASSERT_EQUAL(
+ INetContentTypeParameterList::size_type(2), parameters.size());
+ auto i = parameters.find("parm1"_ostr);
+ CPPUNIT_ASSERT(i != parameters.end());
+ CPPUNIT_ASSERT_EQUAL(OString(), i->second.m_sCharset);
+ CPPUNIT_ASSERT_EQUAL(OString(), i->second.m_sLanguage);
+ CPPUNIT_ASSERT_EQUAL(OUString("Value1"), i->second.m_sValue);
+ CPPUNIT_ASSERT(i->second.m_bConverted);
+ i = parameters.find("parm2"_ostr);
+ CPPUNIT_ASSERT(i != parameters.end());
+ CPPUNIT_ASSERT_EQUAL(OString(), i->second.m_sCharset);
+ CPPUNIT_ASSERT_EQUAL(OString(), i->second.m_sLanguage);
+ CPPUNIT_ASSERT_EQUAL(OUString("unpacked value; %20"), i->second.m_sValue);
+ CPPUNIT_ASSERT(i->second.m_bConverted);
+ }
+
+ void Test::test_scanContentType_rfc2231()
+ {
+ // Test extended parameter with value split in 3 sections:
+ OUString input
+ = "TEST/subTST; "
+ "parm1*0*=US-ASCII'En'5%25%20; "
+ "Parm1*1*=of%2010;\t"
+ "parm1*2*=%20%3d%200.5";
+ // Just scan input for valid string:
+ auto end = INetMIME::scanContentType(input);
+ CPPUNIT_ASSERT(end != nullptr);
+ CPPUNIT_ASSERT_EQUAL(OUString(), OUString(end));
+ // Scan input and parse type, subType and parameters:
+ OUString type;
+ OUString subType;
+ INetContentTypeParameterList parameters;
+ end = INetMIME::scanContentType(input,
+ &type, &subType, &parameters);
+ CPPUNIT_ASSERT(end != nullptr);
+ CPPUNIT_ASSERT_EQUAL(OUString(), OUString(end));
+ CPPUNIT_ASSERT_EQUAL(OUString("test"), type);
+ CPPUNIT_ASSERT_EQUAL(OUString("subtst"), subType);
+ CPPUNIT_ASSERT_EQUAL(
+ INetContentTypeParameterList::size_type(1), parameters.size());
+ auto i = parameters.find("parm1"_ostr);
+ CPPUNIT_ASSERT(i != parameters.end());
+ CPPUNIT_ASSERT_EQUAL("us-ascii"_ostr, i->second.m_sCharset);
+ CPPUNIT_ASSERT_EQUAL("en"_ostr, i->second.m_sLanguage);
+ CPPUNIT_ASSERT_EQUAL(OUString("5% of 10 = 0.5"), i->second.m_sValue);
+ CPPUNIT_ASSERT(i->second.m_bConverted);
+
+ // Test extended parameters with different value charsets:
+ input = "TEST/subTST;"
+ "parm1*0*=us-ascii'en'value;PARM1*1*=1;"
+ "parm2*0*=WINDOWS-1250'en-GB'value2%20%80;"
+ "parm3*0*=UNKNOWN'EN'value3;"
+ "parm1*1*=2"; // this parameter is a duplicate,
+ // the scan should end before this parameter
+ // Just scan input for valid string:
+ end = INetMIME::scanContentType(input);
+ CPPUNIT_ASSERT(end != nullptr);
+ CPPUNIT_ASSERT_EQUAL(OUString(";parm1*1*=2"), OUString(end)); // the invalid end of input
+ // Scan input and parse type, subType and parameters:
+ end = INetMIME::scanContentType(input,
+ &type, &subType, &parameters);
+ CPPUNIT_ASSERT(end != nullptr);
+ CPPUNIT_ASSERT_EQUAL(OUString(";parm1*1*=2"), OUString(end)); // the invalid end of input
+ CPPUNIT_ASSERT_EQUAL(OUString("test"), type);
+ CPPUNIT_ASSERT_EQUAL(OUString("subtst"), subType);
+ CPPUNIT_ASSERT_EQUAL(
+ INetContentTypeParameterList::size_type(3), parameters.size());
+ i = parameters.find("parm1"_ostr);
+ CPPUNIT_ASSERT(i != parameters.end());
+ CPPUNIT_ASSERT_EQUAL("us-ascii"_ostr, i->second.m_sCharset);
+ CPPUNIT_ASSERT_EQUAL("en"_ostr, i->second.m_sLanguage);
+ CPPUNIT_ASSERT_EQUAL(OUString("value1"), i->second.m_sValue);
+ CPPUNIT_ASSERT(i->second.m_bConverted);
+ i = parameters.find("parm2"_ostr);
+ CPPUNIT_ASSERT(i != parameters.end());
+ CPPUNIT_ASSERT_EQUAL("windows-1250"_ostr, i->second.m_sCharset);
+ CPPUNIT_ASSERT_EQUAL("en-gb"_ostr, i->second.m_sLanguage);
+ // Euro currency sign, windows-1250 x80 is converted to unicode u20AC:
+ CPPUNIT_ASSERT_EQUAL(u"value2 \u20AC"_ustr, i->second.m_sValue);
+ CPPUNIT_ASSERT(i->second.m_bConverted);
+ i = parameters.find("parm3"_ostr);
+ CPPUNIT_ASSERT(i != parameters.end());
+ CPPUNIT_ASSERT_EQUAL("unknown"_ostr, i->second.m_sCharset);
+ CPPUNIT_ASSERT_EQUAL("en"_ostr, i->second.m_sLanguage);
+ // Conversion fails for unknown charsets:
+ CPPUNIT_ASSERT(!i->second.m_bConverted);
+ }
+
+ CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_json_writer.cxx b/tools/qa/cppunit/test_json_writer.cxx
new file mode 100644
index 0000000000..a82fc769be
--- /dev/null
+++ b/tools/qa/cppunit/test_json_writer.cxx
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <o3tl/deleter.hxx>
+#include <rtl/ustring.hxx>
+#include <tools/json_writer.hxx>
+
+namespace
+{
+class JsonWriterTest : public CppUnit::TestFixture
+{
+public:
+ void test1();
+ void test2();
+ void testArray();
+
+ CPPUNIT_TEST_SUITE(JsonWriterTest);
+ CPPUNIT_TEST(test1);
+ CPPUNIT_TEST(test2);
+ CPPUNIT_TEST(testArray);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void JsonWriterTest::test1()
+{
+ tools::JsonWriter aJson;
+
+ {
+ auto testNode = aJson.startNode("node");
+ aJson.put("oustring", OUString("val1"));
+ aJson.put("charptr", "val3");
+ aJson.put("int", static_cast<sal_Int32>(12));
+ }
+
+ OString result(aJson.finishAndGetAsOString());
+
+ CPPUNIT_ASSERT_EQUAL("{ \"node\": { \"oustring\": \"val1\", "
+ "\"charptr\": \"val3\", \"int\": 12}}"_ostr,
+ result);
+}
+
+void JsonWriterTest::test2()
+{
+ tools::JsonWriter aJson;
+
+ {
+ auto testNode = aJson.startNode("node");
+ aJson.put("field1", OUString("val1"));
+ aJson.put("field2", OUString("val2"));
+ {
+ auto testNode2 = aJson.startNode("node");
+ aJson.put("field3", OUString("val3"));
+ {
+ auto testNode3 = aJson.startNode("node");
+ aJson.put("field4", OUString("val4"));
+ aJson.put("field5", OUString("val5"));
+ }
+ }
+ }
+
+ OString result(aJson.finishAndGetAsOString());
+
+ CPPUNIT_ASSERT_EQUAL("{ \"node\": { \"field1\": \"val1\", \"field2\": \"val2\", "
+ "\"node\": { \"field3\": \"val3\", \"node\": { \"field4\": "
+ "\"val4\", \"field5\": \"val5\"}}}}"_ostr,
+ result);
+}
+
+void JsonWriterTest::testArray()
+{
+ tools::JsonWriter aJson;
+ {
+ tools::ScopedJsonWriterArray aArray = aJson.startArray("items");
+ aJson.putSimpleValue("foo");
+ aJson.putSimpleValue("bar");
+ }
+
+ OString aResult(aJson.finishAndGetAsOString());
+
+ CPPUNIT_ASSERT_EQUAL("{ \"items\": [ \"foo\", \"bar\"]}"_ostr, aResult);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(JsonWriterTest);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_pathutils.cxx b/tools/qa/cppunit/test_pathutils.cxx
new file mode 100644
index 0000000000..7ad75f1e8d
--- /dev/null
+++ b/tools/qa/cppunit/test_pathutils.cxx
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cwchar>
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <tools/pathutils.hxx>
+
+namespace
+{
+void buildPath(wchar_t const* front, wchar_t const* back, wchar_t const* path)
+{
+#if defined(_WIN32)
+ wchar_t p[MAX_PATH];
+ wchar_t* e = tools::buildPath(p, front, front + std::wcslen(front), back, std::wcslen(back));
+ CPPUNIT_ASSERT_EQUAL(static_cast<void*>(p + std::wcslen(path)), static_cast<void*>(e));
+ CPPUNIT_ASSERT_EQUAL(0, std::wcscmp(path, p));
+#else
+ (void)front;
+ (void)back;
+ (void)path;
+#endif
+}
+
+class Test : public CppUnit::TestFixture
+{
+public:
+ void testBuildPath();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(testBuildPath);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void Test::testBuildPath()
+{
+ buildPath(L"a:\\b\\", L"..", L"a:\\");
+ buildPath(L"a:\\b\\", L"..\\", L"a:\\");
+ buildPath(L"a:\\b\\c\\", L"..\\..\\..\\d", L"a:\\..\\d");
+ buildPath(L"\\\\a\\b\\", L"..\\..\\..\\c", L"\\\\..\\c");
+ buildPath(L"\\", L"..\\a", L"\\..\\a");
+ buildPath(L"", L"..\\a", L"..\\a");
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_poly.cxx b/tools/qa/cppunit/test_poly.cxx
new file mode 100644
index 0000000000..f966f5317a
--- /dev/null
+++ b/tools/qa/cppunit/test_poly.cxx
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/poly.hxx>
+
+namespace
+{
+class Test : public CppUnit::TestFixture
+{
+public:
+ void testTdf137068();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(testTdf137068);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void Test::testTdf137068()
+{
+ // Make sure PolyPolygon::Clip() does not break bezier curves.
+ const Point points[] = { { 1337, 411 }, { 1337, 471 }, { 1313, 530 }, { 1268, 582 } };
+ const PolyFlags flags[]
+ = { PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control, PolyFlags::Normal };
+ tools::Polygon polygon(4, points, flags);
+ tools::PolyPolygon polyPolygon(polygon);
+ polyPolygon.Clip(tools::Rectangle(Point(0, 0), Size(1920, 1080)));
+ // operator== is stupid and just compares pointers, compare data manually
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), polyPolygon.Count());
+ tools::Polygon result = polyPolygon.GetObject(0);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(4), result.GetSize());
+ CPPUNIT_ASSERT_EQUAL(points[0], result.GetPoint(0));
+ CPPUNIT_ASSERT_EQUAL(points[1], result.GetPoint(1));
+ CPPUNIT_ASSERT_EQUAL(points[2], result.GetPoint(2));
+ CPPUNIT_ASSERT_EQUAL(points[3], result.GetPoint(3));
+ CPPUNIT_ASSERT_EQUAL(flags[0], result.GetFlags(0));
+ CPPUNIT_ASSERT_EQUAL(flags[1], result.GetFlags(1));
+ CPPUNIT_ASSERT_EQUAL(flags[2], result.GetFlags(2));
+ CPPUNIT_ASSERT_EQUAL(flags[3], result.GetFlags(3));
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_rectangle.cxx b/tools/qa/cppunit/test_rectangle.cxx
new file mode 100644
index 0000000000..12e46910bc
--- /dev/null
+++ b/tools/qa/cppunit/test_rectangle.cxx
@@ -0,0 +1,254 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <tools/gen.hxx>
+
+namespace
+{
+class RectangleTest : public CppUnit::TestFixture
+{
+public:
+ void testConstruction();
+ void testOpenClosedSize();
+ void testUnitConvesion();
+ void testSetOperators();
+ void test_rectnormalize_alreadynormal();
+ void test_rectnormalize_zerorect();
+ void test_rectnormalize_reverse_topleft_bottomright();
+ void test_rectnormalize_topright_bottomleft();
+ void test_rectnormalize_bottomleft_topright();
+ void test_rectnormalize_zerowidth_top_bottom_reversed();
+ void test_rectnormalize_zeroheight_left_right_reversed();
+
+ CPPUNIT_TEST_SUITE(RectangleTest);
+ CPPUNIT_TEST(testConstruction);
+ CPPUNIT_TEST(testOpenClosedSize);
+ CPPUNIT_TEST(testUnitConvesion);
+ CPPUNIT_TEST(testSetOperators);
+ CPPUNIT_TEST(test_rectnormalize_zerorect);
+ CPPUNIT_TEST(test_rectnormalize_alreadynormal);
+ CPPUNIT_TEST(test_rectnormalize_reverse_topleft_bottomright);
+ CPPUNIT_TEST(test_rectnormalize_topright_bottomleft);
+ CPPUNIT_TEST(test_rectnormalize_bottomleft_topright);
+ CPPUNIT_TEST(test_rectnormalize_zerowidth_top_bottom_reversed);
+ CPPUNIT_TEST(test_rectnormalize_zeroheight_left_right_reversed);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void RectangleTest::testConstruction()
+{
+ {
+ tools::Rectangle aRect1(Point(), Size(0, 20));
+ CPPUNIT_ASSERT_EQUAL(true, aRect1.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect1.getOpenWidth());
+
+ tools::Rectangle aRect2{ Point(), Point(0, 20) };
+ CPPUNIT_ASSERT_EQUAL(false, aRect2.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect2.getOpenWidth());
+
+ tools::Rectangle aRect3(0, 0, 0, 20);
+ CPPUNIT_ASSERT_EQUAL(false, aRect3.IsEmpty());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect3.getOpenWidth());
+ }
+ {
+ constexpr tools::Rectangle aRect(Point(), Size(-1, -2));
+ static_assert(!aRect.IsEmpty());
+ static_assert(aRect.Right() == 0);
+ static_assert(aRect.Bottom() == -1);
+
+ tools::Rectangle aRect2;
+ aRect2.SetSize(Size(-1, -2));
+ CPPUNIT_ASSERT_EQUAL(aRect, aRect2);
+
+ constexpr tools::Rectangle aRect3(Point(), Size(0, 0));
+ static_assert(aRect3.IsEmpty());
+ static_assert(aRect3.Right() == 0);
+ static_assert(aRect3.Bottom() == 0);
+
+ constexpr tools::Rectangle aRect4(Point(), Size(1, 1));
+ static_assert(!aRect4.IsEmpty());
+ static_assert(aRect4.Right() == 0);
+ static_assert(aRect4.Bottom() == 0);
+
+ constexpr tools::Rectangle aRect5(Point(), Size(-1, -1));
+ static_assert(!aRect5.IsEmpty());
+ static_assert(aRect5.Right() == 0);
+ static_assert(aRect5.Bottom() == 0);
+ }
+}
+
+void RectangleTest::testOpenClosedSize()
+{
+ {
+ tools::Rectangle aRect(1, 1, 1, 1);
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetWidth());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetHeight());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.getOpenWidth());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.getOpenHeight());
+ }
+
+ {
+ tools::Rectangle aRect(Point(), Size(1, 1));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.Left());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.Top());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.Right());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.Bottom());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetWidth());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetHeight());
+
+ // getOpenWidth and getOpenHeight return the size that excludes one of the bounds,
+ // unlike the ctor and GetWidth / GetHeight that operate on inclusive size
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.getOpenWidth());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), aRect.getOpenHeight());
+
+ aRect.SetPosX(12);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetHeight());
+ aRect.SetPosY(12);
+ CPPUNIT_ASSERT_EQUAL(tools::Long(1), aRect.GetWidth());
+ }
+}
+
+void RectangleTest::testUnitConvesion()
+{
+ {
+ constexpr tools::Rectangle aRectTwip(100, 100, 100, 100);
+ constexpr tools::Rectangle aRectMm100(
+ o3tl::convert(aRectTwip, o3tl::Length::twip, o3tl::Length::mm100));
+ static_assert(!aRectMm100.IsEmpty());
+ // Make sure that we use coordinates for conversion, not width/height:
+ // the latter is ambiguous, and e.g. GetWidth(aRectTwip) gives 1, which
+ // would had been converted to 2, resulting in different LR coordinates
+ static_assert(aRectMm100.Left() == aRectMm100.Right());
+ static_assert(aRectMm100.Top() == aRectMm100.Bottom());
+ }
+
+ {
+ constexpr tools::Rectangle aRectTwip(1, 1);
+ constexpr tools::Rectangle aRectMm100(
+ o3tl::convert(aRectTwip, o3tl::Length::twip, o3tl::Length::mm100));
+ // Make sure that result keeps the empty flag
+ static_assert(aRectMm100.IsEmpty());
+ static_assert(aRectMm100.IsWidthEmpty());
+ static_assert(aRectMm100.IsHeightEmpty());
+ static_assert(aRectMm100.GetWidth() == 0);
+ static_assert(aRectMm100.GetHeight() == 0);
+ }
+}
+
+void RectangleTest::testSetOperators()
+{
+ constexpr tools::Rectangle rect(Point(0, 0), Size(20, 20));
+ constexpr tools::Rectangle inside(Point(10, 10), Size(10, 10));
+ constexpr tools::Rectangle overlap(Point(10, 10), Size(20, 20));
+ constexpr tools::Rectangle outside(Point(20, 20), Size(10, 10));
+ CPPUNIT_ASSERT(rect.Contains(inside));
+ CPPUNIT_ASSERT(rect.Contains(rect));
+ CPPUNIT_ASSERT(!rect.Contains(overlap));
+ CPPUNIT_ASSERT(!rect.Contains(outside));
+ CPPUNIT_ASSERT(rect.Overlaps(inside));
+ CPPUNIT_ASSERT(rect.Overlaps(rect));
+ CPPUNIT_ASSERT(rect.Overlaps(overlap));
+ CPPUNIT_ASSERT(!rect.Overlaps(outside));
+}
+
+void RectangleTest::test_rectnormalize_alreadynormal()
+{
+ Point aTopLeft(0, 0);
+ Point aBottomRight(1, 1);
+
+ tools::Rectangle aRect(aTopLeft, aBottomRight);
+ aRect.Normalize();
+
+ CPPUNIT_ASSERT_EQUAL(aRect.TopLeft(), aTopLeft);
+ CPPUNIT_ASSERT_EQUAL(aRect.BottomRight(), aBottomRight);
+}
+
+void RectangleTest::test_rectnormalize_zerorect()
+{
+ Point aTopLeft(53, 53);
+ Point aBottomRight(53, 53);
+
+ tools::Rectangle aRect(aTopLeft, aBottomRight);
+ aRect.Normalize();
+
+ CPPUNIT_ASSERT_EQUAL(aRect.TopLeft(), aTopLeft);
+ CPPUNIT_ASSERT_EQUAL(aRect.BottomRight(), aBottomRight);
+}
+
+void RectangleTest::test_rectnormalize_reverse_topleft_bottomright()
+{
+ Point aPoint1(1, 1);
+ Point aPoint2(0, 0);
+
+ tools::Rectangle aRect(aPoint1, aPoint2);
+ aRect.Normalize();
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("TopLeft() is wrong", Point(0, 0), aRect.TopLeft());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("BottomRight() is wrong", Point(1, 1), aRect.BottomRight());
+}
+
+void RectangleTest::test_rectnormalize_topright_bottomleft()
+{
+ Point aPoint1(1, 0);
+ Point aPoint2(0, 1);
+
+ tools::Rectangle aRect(aPoint1, aPoint2);
+ aRect.Normalize();
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("TopLeft() is wrong", Point(0, 0), aRect.TopLeft());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("BottomRight() is wrong", Point(1, 1), aRect.BottomRight());
+}
+
+void RectangleTest::test_rectnormalize_bottomleft_topright()
+{
+ Point aPoint1(0, 1);
+ Point aPoint2(1, 0);
+
+ tools::Rectangle aRect(aPoint1, aPoint2);
+ aRect.Normalize();
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("TopLeft() is wrong", Point(0, 0), aRect.TopLeft());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("BottomRight() is wrong", Point(1, 1), aRect.BottomRight());
+}
+
+void RectangleTest::test_rectnormalize_zerowidth_top_bottom_reversed()
+{
+ Point aPoint1(0, 1);
+ Point aPoint2(0, 0);
+
+ tools::Rectangle aRect(aPoint1, aPoint2);
+ aRect.Normalize();
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("TopLeft() is wrong", Point(0, 0), aRect.TopLeft());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("BottomRight() is wrong", Point(0, 1), aRect.BottomRight());
+}
+
+void RectangleTest::test_rectnormalize_zeroheight_left_right_reversed()
+{
+ Point aPoint1(1, 0);
+ Point aPoint2(0, 0);
+
+ tools::Rectangle aRect(aPoint1, aPoint2);
+ aRect.Normalize();
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("TopLeft() is wrong", Point(0, 0), aRect.TopLeft());
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("BottomRight() is wrong", Point(1, 0), aRect.BottomRight());
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(RectangleTest);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_reversemap.cxx b/tools/qa/cppunit/test_reversemap.cxx
new file mode 100644
index 0000000000..be70ade795
--- /dev/null
+++ b/tools/qa/cppunit/test_reversemap.cxx
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/types.h>
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+#include <rtl/ustring.hxx>
+#include <vector>
+
+#include <tools/tenccvt.hxx>
+
+//Tests for getBestMSEncodingByChar
+
+namespace
+{
+
+ class Test: public CppUnit::TestFixture
+ {
+ public:
+ void testEncoding(rtl_TextEncoding eEncoding);
+
+ void test1258();
+ void test1257();
+ void test1256();
+ void test1255();
+ void test1254();
+ void test1253();
+ void test1252();
+ void test1251();
+ void test1250();
+ void test874();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(test1258);
+ CPPUNIT_TEST(test1257);
+ CPPUNIT_TEST(test1256);
+ CPPUNIT_TEST(test1255);
+ CPPUNIT_TEST(test1254);
+ CPPUNIT_TEST(test1253);
+ CPPUNIT_TEST(test1252);
+ CPPUNIT_TEST(test1251);
+ CPPUNIT_TEST(test1250);
+ CPPUNIT_TEST(test874);
+ CPPUNIT_TEST_SUITE_END();
+ };
+
+ void Test::testEncoding(rtl_TextEncoding eEncoding)
+ {
+ //Taking the single byte legacy encodings, fill in all possible values
+ std::vector<char> aAllChars(255);
+ for (int i = 1; i <= 255; ++i)
+ aAllChars[i-1] = static_cast<char>(i);
+
+ //Some slots are unused, so don't map to private, just set them to 'X'
+ sal_uInt32 const convertFlags = OSTRING_TO_OUSTRING_CVTFLAGS ^ RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_MAPTOPRIVATE;
+ OUString sOrigText(aAllChars.data(), aAllChars.size(), eEncoding, convertFlags);
+ sOrigText = sOrigText.replace( 0xfffd, 'X' );
+
+ //Should clearly be equal
+ sal_Int32 nLength = aAllChars.size();
+ CPPUNIT_ASSERT_EQUAL(sOrigText.getLength(), nLength);
+
+ OUString sFinalText;
+
+ //Split up in chunks of the same encoding returned by
+ //getBestMSEncodingByChar, convert to it, and back
+ rtl_TextEncoding ePrevEncoding = RTL_TEXTENCODING_DONTKNOW;
+ const sal_Unicode *pStr = sOrigText.getStr();
+ sal_Int32 nChunkStart=0;
+ for (int i = 0; i < 255; ++i)
+ {
+ rtl_TextEncoding eCurrEncoding = getBestMSEncodingByChar(pStr[i]);
+ if (eCurrEncoding != ePrevEncoding)
+ {
+ OString aChunk(pStr+nChunkStart, i-nChunkStart, ePrevEncoding);
+ sFinalText += OStringToOUString(aChunk, ePrevEncoding);
+ nChunkStart = i;
+ }
+ ePrevEncoding = eCurrEncoding;
+ }
+ if (nChunkStart < 255)
+ {
+ OString aChunk(pStr+nChunkStart, 255-nChunkStart, ePrevEncoding);
+ sFinalText += OStringToOUString(aChunk, ePrevEncoding);
+ }
+
+ //Final text should be the same as original
+ CPPUNIT_ASSERT_EQUAL(sOrigText, sFinalText);
+ }
+
+ void Test::test1252()
+ {
+ testEncoding(RTL_TEXTENCODING_MS_1252);
+ }
+
+ void Test::test874()
+ {
+ testEncoding(RTL_TEXTENCODING_MS_874);
+ }
+
+ void Test::test1258()
+ {
+ testEncoding(RTL_TEXTENCODING_MS_1258);
+ }
+
+ void Test::test1257()
+ {
+ testEncoding(RTL_TEXTENCODING_MS_1257);
+ }
+
+ void Test::test1256()
+ {
+ testEncoding(RTL_TEXTENCODING_MS_1256);
+ }
+
+ void Test::test1255()
+ {
+ testEncoding(RTL_TEXTENCODING_MS_1255);
+ }
+
+ void Test::test1254()
+ {
+ testEncoding(RTL_TEXTENCODING_MS_1254);
+ }
+
+ void Test::test1253()
+ {
+ testEncoding(RTL_TEXTENCODING_MS_1253);
+ }
+
+ void Test::test1251()
+ {
+ testEncoding(RTL_TEXTENCODING_MS_1251);
+ }
+
+ void Test::test1250()
+ {
+ testEncoding(RTL_TEXTENCODING_MS_1250);
+ }
+
+ CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_stream.cxx b/tools/qa/cppunit/test_stream.cxx
new file mode 100644
index 0000000000..edec9c0fb7
--- /dev/null
+++ b/tools/qa/cppunit/test_stream.cxx
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/types.h>
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <tools/stream.hxx>
+#include <unotools/tempfile.hxx>
+#include <sstream>
+
+//Tests for eofbit/badbit/goodbit/failbit
+
+namespace
+{
+
+ class Test: public CppUnit::TestFixture
+ {
+ public:
+ void test_stdstream();
+ void test_fastostring();
+ void test_read_cstring();
+ void test_read_pstring();
+ void test_readline();
+ void test_makereadonly();
+ void test_write_unicode();
+
+ CPPUNIT_TEST_SUITE(Test);
+ CPPUNIT_TEST(test_stdstream);
+ CPPUNIT_TEST(test_fastostring);
+ CPPUNIT_TEST(test_read_cstring);
+ CPPUNIT_TEST(test_read_pstring);
+ CPPUNIT_TEST(test_readline);
+ CPPUNIT_TEST(test_makereadonly);
+ CPPUNIT_TEST(test_write_unicode);
+ CPPUNIT_TEST_SUITE_END();
+ };
+
+ void Test::test_stdstream()
+ {
+ char foo[] = "foo";
+ std::istringstream iss(foo, std::istringstream::in);
+ SvMemoryStream aMemStream(foo, SAL_N_ELEMENTS(foo)-1, StreamMode::READ);
+
+ char std_a(78);
+ iss >> std_a;
+ CPPUNIT_ASSERT_EQUAL('f', std_a);
+
+ char tools_a(78);
+ aMemStream.ReadChar( tools_a );
+ CPPUNIT_ASSERT_EQUAL('f', tools_a);
+
+ iss.seekg(0, std::ios_base::end);
+ //seeking to end doesn't set eof, reading past eof does
+ CPPUNIT_ASSERT(!iss.eof());
+ CPPUNIT_ASSERT(iss.good());
+
+ aMemStream.Seek(STREAM_SEEK_TO_END);
+ //seeking to end doesn't set eof, reading past eof does
+ CPPUNIT_ASSERT(!aMemStream.eof());
+ CPPUNIT_ASSERT(aMemStream.good());
+
+ std_a = 78;
+ iss >> std_a;
+ //so, now eof is set
+ CPPUNIT_ASSERT(iss.eof());
+ //a failed read doesn't change the data, it remains unchanged
+ CPPUNIT_ASSERT_EQUAL(static_cast<char>(78), std_a);
+ //nothing wrong with the stream, so not bad
+ CPPUNIT_ASSERT(!iss.bad());
+ //yet, the read didn't succeed
+ CPPUNIT_ASSERT(!iss.good());
+ CPPUNIT_ASSERT_EQUAL((std::ios::failbit|std::ios::eofbit), iss.rdstate());
+
+ tools_a = 78;
+ aMemStream.ReadChar( tools_a );
+ //so, now eof is set
+ CPPUNIT_ASSERT(aMemStream.eof());
+ //a failed read doesn't change the data, it remains unchanged
+ CPPUNIT_ASSERT_EQUAL(static_cast<char>(78), tools_a);
+ //nothing wrong with the stream, so not bad
+ CPPUNIT_ASSERT(!aMemStream.bad());
+ //yet, the read didn't succeed
+ CPPUNIT_ASSERT(!aMemStream.good());
+
+ //set things up so that there is only one byte available on an attempt
+ //to read a two-byte sal_uInt16. The byte should be consumed, but the
+ //operation should fail, and tools_b should remain unchanged,
+ sal_uInt16 tools_b = 0x1122;
+ aMemStream.SeekRel(-1);
+ CPPUNIT_ASSERT(!aMemStream.eof());
+ CPPUNIT_ASSERT(aMemStream.good());
+ aMemStream.ReadUInt16( tools_b );
+ CPPUNIT_ASSERT(!aMemStream.good());
+ CPPUNIT_ASSERT(aMemStream.eof());
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(0x1122), tools_b);
+
+ iss.clear();
+ iss.seekg(0);
+ CPPUNIT_ASSERT(iss.good());
+ iss >> std_a;
+ CPPUNIT_ASSERT_EQUAL('f', std_a);
+
+ aMemStream.Seek(0);
+ CPPUNIT_ASSERT(aMemStream.good());
+ aMemStream.ReadChar( tools_a );
+ CPPUNIT_ASSERT_EQUAL('f', tools_a);
+
+ //failbit is rather subtle wrt e.g seeks
+
+ char buffer[1024];
+
+ iss.clear();
+ iss.seekg(0);
+ CPPUNIT_ASSERT(iss.good());
+ iss.read(buffer, sizeof(buffer));
+ CPPUNIT_ASSERT_EQUAL(static_cast<std::streamsize>(3), iss.gcount());
+ CPPUNIT_ASSERT(!iss.good());
+ CPPUNIT_ASSERT(!iss.bad());
+ CPPUNIT_ASSERT(iss.eof());
+
+ aMemStream.Seek(0);
+ CPPUNIT_ASSERT(aMemStream.good());
+ std::size_t nRet = aMemStream.ReadBytes(buffer, sizeof(buffer));
+ CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(3), nRet);
+ CPPUNIT_ASSERT(!aMemStream.good());
+ CPPUNIT_ASSERT(!aMemStream.bad());
+ CPPUNIT_ASSERT(aMemStream.eof());
+ }
+
+ void Test::test_fastostring()
+ {
+ char foo[] = "foobar";
+ SvMemoryStream aMemStream(foo, SAL_N_ELEMENTS(foo)-1, StreamMode::READ);
+
+ OString aOne = read_uInt8s_ToOString(aMemStream, 3);
+ CPPUNIT_ASSERT_EQUAL("foo"_ostr, aOne);
+
+ OString aTwo = read_uInt8s_ToOString(aMemStream, 3);
+ CPPUNIT_ASSERT_EQUAL("bar"_ostr, aTwo);
+
+ OString aThree = read_uInt8s_ToOString(aMemStream, 3);
+ CPPUNIT_ASSERT(aThree.isEmpty());
+
+ aMemStream.Seek(0);
+
+ OString aFour = read_uInt8s_ToOString(aMemStream, 100);
+ CPPUNIT_ASSERT_EQUAL(OString(foo), aFour);
+ }
+
+ void Test::test_read_cstring()
+ {
+ char foo[] = "foobar";
+ SvMemoryStream aMemStream(foo, SAL_N_ELEMENTS(foo)-1, StreamMode::READ);
+
+ OString aOne = read_zeroTerminated_uInt8s_ToOString(aMemStream);
+ CPPUNIT_ASSERT_EQUAL("foobar"_ostr, aOne);
+ CPPUNIT_ASSERT(!aMemStream.good());
+ CPPUNIT_ASSERT(!aMemStream.bad());
+ CPPUNIT_ASSERT(aMemStream.eof());
+
+ aMemStream.Seek(0);
+ foo[3] = 0;
+ OString aTwo = read_zeroTerminated_uInt8s_ToOString(aMemStream);
+ CPPUNIT_ASSERT_EQUAL("foo"_ostr, aTwo);
+ CPPUNIT_ASSERT(aMemStream.good());
+ }
+
+ void Test::test_read_pstring()
+ {
+ char foo[] = "\3foobar";
+ SvMemoryStream aMemStream(foo, SAL_N_ELEMENTS(foo)-1, StreamMode::READ);
+
+ OString aFoo = read_uInt8_lenPrefixed_uInt8s_ToOString(aMemStream);
+ CPPUNIT_ASSERT_EQUAL("foo"_ostr, aFoo);
+ CPPUNIT_ASSERT(aMemStream.good());
+ CPPUNIT_ASSERT(!aMemStream.bad());
+ CPPUNIT_ASSERT(!aMemStream.eof());
+
+ aMemStream.Seek(0);
+ foo[0] = 10;
+ aFoo = read_uInt8_lenPrefixed_uInt8s_ToOString(aMemStream);
+ CPPUNIT_ASSERT_EQUAL("foobar"_ostr, aFoo);
+ CPPUNIT_ASSERT(!aMemStream.good());
+ CPPUNIT_ASSERT(!aMemStream.bad());
+ CPPUNIT_ASSERT(aMemStream.eof());
+
+ aMemStream.SetEndian(SvStreamEndian::BIG);
+ aMemStream.Seek(0);
+ foo[0] = 0;
+ foo[1] = 3;
+ aFoo = read_uInt16_lenPrefixed_uInt8s_ToOString(aMemStream);
+ CPPUNIT_ASSERT_EQUAL("oob"_ostr, aFoo);
+ CPPUNIT_ASSERT(aMemStream.good());
+ CPPUNIT_ASSERT(!aMemStream.bad());
+ CPPUNIT_ASSERT(!aMemStream.eof());
+ }
+
+ void Test::test_readline()
+ {
+ char foo[] = "foo\nbar\n\n";
+ SvMemoryStream aMemStream(foo, SAL_N_ELEMENTS(foo)-1, StreamMode::READ);
+
+ OString aFoo;
+ bool bRet;
+
+ bRet = aMemStream.ReadLine(aFoo);
+ CPPUNIT_ASSERT(bRet);
+ CPPUNIT_ASSERT_EQUAL("foo"_ostr, aFoo);
+ CPPUNIT_ASSERT(aMemStream.good());
+
+ bRet = aMemStream.ReadLine(aFoo);
+ CPPUNIT_ASSERT(bRet);
+ CPPUNIT_ASSERT_EQUAL("bar"_ostr, aFoo);
+ CPPUNIT_ASSERT(aMemStream.good());
+
+ bRet = aMemStream.ReadLine(aFoo);
+ CPPUNIT_ASSERT(bRet);
+ CPPUNIT_ASSERT(aFoo.isEmpty());
+ CPPUNIT_ASSERT(aMemStream.good());
+
+ bRet = aMemStream.ReadLine(aFoo);
+ CPPUNIT_ASSERT(!bRet);
+ CPPUNIT_ASSERT(aFoo.isEmpty());
+ CPPUNIT_ASSERT(aMemStream.eof());
+
+ foo[3] = 0; //test reading embedded nulls
+
+ aMemStream.Seek(0);
+ bRet = aMemStream.ReadLine(aFoo);
+ CPPUNIT_ASSERT(bRet);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aFoo.getLength());
+ CPPUNIT_ASSERT_EQUAL('\0', aFoo[3]);
+ CPPUNIT_ASSERT(aMemStream.good());
+
+ std::string sStr(foo, RTL_CONSTASCII_LENGTH(foo));
+ std::istringstream iss(sStr, std::istringstream::in);
+ std::getline(iss, sStr, '\n');
+ //embedded null read as expected
+ CPPUNIT_ASSERT_EQUAL(std::string::size_type(7), sStr.size());
+ CPPUNIT_ASSERT_EQUAL('\0', sStr[3]);
+ CPPUNIT_ASSERT(iss.good());
+
+ bRet = aMemStream.ReadLine(aFoo);
+ CPPUNIT_ASSERT(bRet);
+ CPPUNIT_ASSERT(aFoo.isEmpty());
+ CPPUNIT_ASSERT(aMemStream.good());
+
+ std::getline(iss, sStr, '\n');
+ CPPUNIT_ASSERT(sStr.empty());
+ CPPUNIT_ASSERT(iss.good());
+
+ bRet = aMemStream.ReadLine(aFoo);
+ CPPUNIT_ASSERT(!bRet);
+ CPPUNIT_ASSERT(aFoo.isEmpty());
+ CPPUNIT_ASSERT(aMemStream.eof());
+ CPPUNIT_ASSERT(!aMemStream.bad());
+
+ std::getline(iss, sStr, '\n');
+ CPPUNIT_ASSERT(sStr.empty());
+ CPPUNIT_ASSERT(iss.eof());
+ CPPUNIT_ASSERT(!iss.bad());
+
+ char bar[] = "foo";
+ SvMemoryStream aMemStreamB(bar, SAL_N_ELEMENTS(bar)-1, StreamMode::READ);
+ bRet = aMemStreamB.ReadLine(aFoo);
+ CPPUNIT_ASSERT(bRet);
+ CPPUNIT_ASSERT_EQUAL("foo"_ostr, aFoo);
+ CPPUNIT_ASSERT(!aMemStreamB.eof()); //<-- diff A
+
+ std::istringstream issB(bar, std::istringstream::in);
+ std::getline(issB, sStr, '\n');
+ CPPUNIT_ASSERT_EQUAL(std::string("foo"), sStr);
+ CPPUNIT_ASSERT(issB.eof()); //<-- diff A
+ }
+
+ void Test::test_makereadonly()
+ {
+ SvMemoryStream aMemStream;
+ CPPUNIT_ASSERT(aMemStream.IsWritable());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(0), aMemStream.GetSize());
+ aMemStream.WriteInt64(21);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aMemStream.GetError());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(8), aMemStream.GetSize());
+
+ aMemStream.MakeReadOnly();
+ CPPUNIT_ASSERT(!aMemStream.IsWritable());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(8), aMemStream.GetSize());
+ aMemStream.WriteInt64(42);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_IO_CANTWRITE, aMemStream.GetError());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(8), aMemStream.GetSize());
+
+ aMemStream.ResetError();
+ // Check that seeking past the end doesn't resize a read-only stream.
+ // This apparently doesn't set an error, but at least it shouldn't
+ // change the size.
+ aMemStream.Seek(1024LL*1024*1024*3);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(8), aMemStream.GetSize());
+
+ aMemStream.ResetError();
+ aMemStream.Seek(0);
+ sal_Int64 res;
+ aMemStream.ReadInt64(res);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aMemStream.GetError());
+ CPPUNIT_ASSERT_EQUAL(sal_Int64(21), res);
+ }
+
+ void Test::test_write_unicode()
+ {
+ constexpr OUString write(u"abc"_ustr);
+ utl::TempFileNamed aTempFile(u"test_write_unicode");
+ aTempFile.EnableKillingFile();
+ {
+ SvStream& s = *aTempFile.GetStream(StreamMode::WRITE);
+ s.SetEndian(SvStreamEndian::BIG);
+ if (!s.IsEndianSwap())
+ s.SetEndian(SvStreamEndian::LITTLE);
+ CPPUNIT_ASSERT(s.IsEndianSwap());
+ // StartWritingUnicodeText must switch to no endian swapping and write 0xfeff
+ s.StartWritingUnicodeText();
+ // Without the fix in place, this would fail
+ CPPUNIT_ASSERT(!s.IsEndianSwap());
+ s.WriteUnicodeOrByteText(write, RTL_TEXTENCODING_UNICODE);
+ aTempFile.CloseStream();
+ }
+ {
+ SvStream& s = *aTempFile.GetStream(StreamMode::READ);
+ s.SetEndian(SvStreamEndian::BIG);
+ if (!s.IsEndianSwap())
+ s.SetEndian(SvStreamEndian::LITTLE);
+ CPPUNIT_ASSERT(s.IsEndianSwap());
+ s.StartReadingUnicodeText(RTL_TEXTENCODING_DONTKNOW);
+ CPPUNIT_ASSERT(!s.IsEndianSwap());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(2), s.Tell()); // after BOM
+ OUString read;
+ CPPUNIT_ASSERT(s.ReadUniOrByteStringLine(read, RTL_TEXTENCODING_UNICODE));
+ // Without the fix in place, this would fail with
+ // - Expected: abc
+ // - Actual : 愀戀挀
+ CPPUNIT_ASSERT_EQUAL(write, read);
+ aTempFile.CloseStream();
+ }
+ }
+
+ CPPUNIT_TEST_SUITE_REGISTRATION(Test);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_time.cxx b/tools/qa/cppunit/test_time.cxx
new file mode 100644
index 0000000000..a21f98d7b4
--- /dev/null
+++ b/tools/qa/cppunit/test_time.cxx
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <tools/time.hxx>
+
+namespace tools
+{
+class TimeTest : public CppUnit::TestFixture
+{
+public:
+ void testTime();
+ void testClockValues();
+
+ CPPUNIT_TEST_SUITE(TimeTest);
+ CPPUNIT_TEST(testTime);
+ CPPUNIT_TEST(testClockValues);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void TimeTest::testTime()
+{
+ Time aOrigTime(1, 56, 10);
+ auto nMS = aOrigTime.GetMSFromTime();
+
+ Time aNewTime(0);
+ aNewTime.MakeTimeFromMS(nMS);
+
+ CPPUNIT_ASSERT(bool(aOrigTime == aNewTime));
+}
+
+void TimeTest::testClockValues()
+{
+ double fTime, fFractionOfSecond;
+ sal_uInt16 nHour, nMinute, nSecond;
+
+ fTime = 0.0;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0);
+
+ fTime = 1.0;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0);
+
+ fTime = -1.0;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0);
+
+ fTime = 1.5;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(12), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0);
+
+ fTime = -1.5;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(12), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0);
+
+ fTime = 0.75;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(18), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0);
+
+ fTime = 0.0208333333333333;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(30), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0);
+
+ fTime = 0.0000115740625;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 3);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond);
+ // Expect this to be a truncated 0.999999
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.999, fFractionOfSecond, 0.0);
+
+ fTime = 0.524268391203704;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 3);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(12), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(34), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(56), nSecond);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.789, fFractionOfSecond, 0.0);
+
+ fTime = -0.000001;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 13);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(23), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(59), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(59), nSecond);
+ // Expect this to be exact within floating point accuracy.
+ // This is a hairy rounding condition, if it yields problems on any
+ // platform feel free to disable the test for that platform.
+ // At least when doing a 32-bit build on Linux x86 with GCC 8.2.1, when -Os from
+ // gb_COMPILEROPTFLAGS in solenv/gbuild/platform/LINUX_INTEL_GCC.mk is overridden by -O1 (or
+ // higher) passed into CXXFLAGS, the test fails with an actual value of 0.9136, for reasons not
+ // investigated further:
+#if !(defined __GNUC__ && !defined __clang__ && defined X86 && defined __OPTIMIZE__ \
+ && !defined __OPTIMIZE_SIZE__)
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.9135999999999, fFractionOfSecond,
+ 1e-14);
+#endif
+
+ fTime = -0.000001;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 4);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(23), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(59), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(59), nSecond);
+ // Expect this to be rounded.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.9136, fFractionOfSecond, 0.0);
+
+ fTime = -0.00000000001;
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 4);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(23), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(59), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(59), nSecond);
+ // Expect this to be a truncated 0.999999
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.9999, fFractionOfSecond, 0.0);
+
+ fTime = -1e-24; // value insignificant for time
+ Time::GetClock(fTime, nHour, nMinute, nSecond, fFractionOfSecond, 0);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Hour value.", sal_uInt16(0), nHour);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Minute value.", sal_uInt16(0), nMinute);
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("Second value.", sal_uInt16(0), nSecond);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Fraction value.", 0.0, fFractionOfSecond, 0.0);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TimeTest);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/qa/cppunit/test_urlobj.cxx b/tools/qa/cppunit/test_urlobj.cxx
new file mode 100644
index 0000000000..fff77e41f5
--- /dev/null
+++ b/tools/qa/cppunit/test_urlobj.cxx
@@ -0,0 +1,382 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <memory>
+#include <string>
+
+#include <sal/types.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+
+#define OUSTR_TO_STDSTR( oustr ) std::string( OUStringToOString( oustr, RTL_TEXTENCODING_ASCII_US ) )
+
+template<> inline std::string CPPUNIT_NS::assertion_traits<INetProtocol>::toString(
+ const INetProtocol& x )
+{
+ OStringStream ost;
+ ost << static_cast<unsigned int>(x);
+ return ost.str();
+}
+
+namespace tools_urlobj
+{
+
+ class urlobjTest:public CppUnit::TestFixture
+ {
+
+ public:
+ // insert your test code here.
+ // this is only demonstration code
+ void urlobjTest_001( )
+ {
+ INetURLObject aUrl( u"file://10.10.1.1/sampledir/sample.file" );
+ CPPUNIT_ASSERT_EQUAL(INetProtocol::File, aUrl.GetProtocol());
+ CPPUNIT_ASSERT_EQUAL(OUString("10.10.1.1"),
+ aUrl.GetHost(INetURLObject::DecodeMechanism::NONE));
+ CPPUNIT_ASSERT_EQUAL(OUString("/sampledir/sample.file"),
+ aUrl.GetURLPath(INetURLObject::DecodeMechanism::NONE));
+ CPPUNIT_ASSERT_EQUAL(OUString("sample.file"),
+ aUrl.getName());
+ CPPUNIT_ASSERT_EQUAL(OUString("sample"), aUrl.getBase());
+ CPPUNIT_ASSERT_EQUAL(OUString("file"), aUrl.getExtension());
+ }
+
+ void urlobjTest_004( )
+ {
+ INetURLObject aUrl( u"smb://10.10.1.1/sampledir/sample.file" );
+ CPPUNIT_ASSERT_EQUAL( INetProtocol::Smb, aUrl.GetProtocol( ) );
+ CPPUNIT_ASSERT_EQUAL(OUString("10.10.1.1"),
+ aUrl.GetHost(INetURLObject::DecodeMechanism::NONE));
+ CPPUNIT_ASSERT_EQUAL(OUString("/sampledir/sample.file"),
+ aUrl.GetURLPath(INetURLObject::DecodeMechanism::NONE));
+ CPPUNIT_ASSERT_EQUAL(OUString("sample.file"), aUrl.getName());
+ CPPUNIT_ASSERT_EQUAL(OUString("sample"), aUrl.getBase());
+ CPPUNIT_ASSERT_EQUAL(OUString("file"), aUrl.getExtension());
+ }
+
+ void urlobjCmisTest( )
+ {
+ // Test with a username part
+ {
+ INetURLObject aUrl( u"vnd.libreoffice.cmis://username@http:%2F%2Ffoo.bar.com:8080%2Fmy%2Fcmis%2Fatom%23repo-id-encoded/path/to/content" );
+ CPPUNIT_ASSERT_EQUAL( std::string( "http://foo.bar.com:8080/my/cmis/atom#repo-id-encoded" ),
+ OUSTR_TO_STDSTR( aUrl.GetHost( INetURLObject::DecodeMechanism::WithCharset ) ) );
+ CPPUNIT_ASSERT_EQUAL( std::string( "username" ), OUSTR_TO_STDSTR( aUrl.GetUser( ) ) );
+ CPPUNIT_ASSERT_EQUAL( std::string( "/path/to/content" ),
+ OUSTR_TO_STDSTR( aUrl.GetURLPath( INetURLObject::DecodeMechanism::NONE ) ) );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong protocol found", INetProtocol::Cmis, aUrl.GetProtocol( ) );
+ }
+
+ // Test without a username part
+ {
+ INetURLObject aUrl(
+ u"vnd.libreoffice.cmis://http:%2F%2Ffoo.bar.com:8080%2Fmy%2Fcmis%2Fatom%23repo-id-encoded/path/to/content" );
+ CPPUNIT_ASSERT_EQUAL( std::string( "http://foo.bar.com:8080/my/cmis/atom#repo-id-encoded" ),
+ OUSTR_TO_STDSTR( aUrl.GetHost( INetURLObject::DecodeMechanism::WithCharset ) ) );
+ CPPUNIT_ASSERT( !aUrl.HasUserData() );
+ CPPUNIT_ASSERT_EQUAL( std::string( "/path/to/content" ),
+ OUSTR_TO_STDSTR( aUrl.GetURLPath( INetURLObject::DecodeMechanism::NONE ) ) );
+ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Wrong protocol found", INetProtocol::Cmis, aUrl.GetProtocol( ) );
+ }
+ }
+
+ void urlobjTest_emptyPath() {
+ {
+ INetURLObject url(u"http://example.com");
+ CPPUNIT_ASSERT_EQUAL(INetProtocol::Http, url.GetProtocol());
+ CPPUNIT_ASSERT_EQUAL(OUString("example.com"), url.GetHost());
+ CPPUNIT_ASSERT_EQUAL(OUString("/"), url.GetURLPath());
+ }
+ {
+ // This is a valid http URL per RFC 7230:
+ INetURLObject url(u"http://example.com?query");
+ CPPUNIT_ASSERT(!url.HasError());
+ }
+ {
+ INetURLObject url(u"http://example.com#fragment");
+ CPPUNIT_ASSERT_EQUAL(INetProtocol::Http, url.GetProtocol());
+ CPPUNIT_ASSERT_EQUAL(OUString("example.com"), url.GetHost());
+ CPPUNIT_ASSERT_EQUAL(OUString("/"), url.GetURLPath());
+ CPPUNIT_ASSERT_EQUAL(OUString("fragment"), url.GetMark());
+ }
+ }
+
+ void urlobjTest_data() {
+ INetURLObject url;
+ std::unique_ptr<SvMemoryStream> strm;
+ unsigned char const * buf;
+
+ url = INetURLObject(u"data:");
+ //TODO: CPPUNIT_ASSERT(url.HasError());
+ strm = url.getData();
+ CPPUNIT_ASSERT(!strm);
+
+ url = INetURLObject(u"data:,");
+ CPPUNIT_ASSERT(!url.HasError());
+ strm = url.getData();
+ CPPUNIT_ASSERT(strm != nullptr);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(0), strm->GetSize());
+ strm.reset();
+
+ url = INetURLObject(u"data:,,%C3%A4%90");
+ CPPUNIT_ASSERT(!url.HasError());
+ strm = url.getData();
+ CPPUNIT_ASSERT(strm != nullptr);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(4), strm->GetSize());
+ buf = static_cast<unsigned char const *>(strm->GetData());
+ CPPUNIT_ASSERT_EQUAL(0x2C, int(buf[0]));
+ CPPUNIT_ASSERT_EQUAL(0xC3, int(buf[1]));
+ CPPUNIT_ASSERT_EQUAL(0xA4, int(buf[2]));
+ CPPUNIT_ASSERT_EQUAL(0x90, int(buf[3]));
+ strm.reset();
+
+ url = INetURLObject(u"data:base64,");
+ //TODO: CPPUNIT_ASSERT(url.HasError());
+ strm = url.getData();
+ CPPUNIT_ASSERT(!strm);
+
+ url = INetURLObject(u"data:;base64,");
+ CPPUNIT_ASSERT(!url.HasError());
+ strm = url.getData();
+ CPPUNIT_ASSERT(strm != nullptr);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(0), strm->GetSize());
+ strm.reset();
+
+ url = INetURLObject(u"data:;bAsE64,");
+ CPPUNIT_ASSERT(!url.HasError());
+ strm = url.getData();
+ CPPUNIT_ASSERT(strm != nullptr);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(0), strm->GetSize());
+ strm.reset();
+
+ url = INetURLObject(u"data:;base64,YWJjCg==");
+ CPPUNIT_ASSERT(!url.HasError());
+ strm = url.getData();
+ CPPUNIT_ASSERT(strm != nullptr);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(4), strm->GetSize());
+ buf = static_cast<unsigned char const *>(strm->GetData());
+ CPPUNIT_ASSERT_EQUAL(0x61, int(buf[0]));
+ CPPUNIT_ASSERT_EQUAL(0x62, int(buf[1]));
+ CPPUNIT_ASSERT_EQUAL(0x63, int(buf[2]));
+ CPPUNIT_ASSERT_EQUAL(0x0A, int(buf[3]));
+ strm.reset();
+
+ url = INetURLObject(u"data:;base64,YWJjCg=");
+ CPPUNIT_ASSERT(!url.HasError());
+ strm = url.getData();
+ CPPUNIT_ASSERT(!strm);
+
+ url = INetURLObject(u"data:;base64,YWJ$Cg==");
+ CPPUNIT_ASSERT(!url.HasError());
+ strm = url.getData();
+ CPPUNIT_ASSERT(!strm);
+
+ url = INetURLObject(u"data:text/plain;param=%22;base64,%22,YQ==");
+ CPPUNIT_ASSERT(!url.HasError());
+ strm = url.getData();
+ CPPUNIT_ASSERT(strm != nullptr);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt64(4), strm->GetSize());
+ buf = static_cast<unsigned char const *>(strm->GetData());
+ CPPUNIT_ASSERT_EQUAL(0x59, int(buf[0]));
+ CPPUNIT_ASSERT_EQUAL(0x51, int(buf[1]));
+ CPPUNIT_ASSERT_EQUAL(0x3D, int(buf[2]));
+ CPPUNIT_ASSERT_EQUAL(0x3D, int(buf[3]));
+ strm.reset();
+
+ url = INetURLObject(u"http://example.com");
+ CPPUNIT_ASSERT(!url.HasError());
+ strm = url.getData();
+ CPPUNIT_ASSERT(!strm);
+ }
+
+ void urlobjTest_isSchemeEqualTo() {
+ CPPUNIT_ASSERT(INetURLObject().isSchemeEqualTo(INetProtocol::NotValid));
+ CPPUNIT_ASSERT(!INetURLObject().isSchemeEqualTo(u""));
+ CPPUNIT_ASSERT(
+ INetURLObject(u"http://example.org").isSchemeEqualTo(
+ INetProtocol::Http));
+ CPPUNIT_ASSERT(
+ !INetURLObject(u"http://example.org").isSchemeEqualTo(
+ INetProtocol::Https));
+ CPPUNIT_ASSERT(
+ INetURLObject(u"http://example.org").isSchemeEqualTo(u"Http"));
+ CPPUNIT_ASSERT(
+ !INetURLObject(u"http://example.org").isSchemeEqualTo(u"dav"));
+ CPPUNIT_ASSERT(
+ INetURLObject(u"dav://example.org").isSchemeEqualTo(u"dav"));
+ }
+
+ void urlobjTest_isAnyKnownWebDAVScheme() {
+ CPPUNIT_ASSERT(
+ INetURLObject(u"http://example.org").isAnyKnownWebDAVScheme());
+ CPPUNIT_ASSERT(
+ INetURLObject(u"https://example.org").isAnyKnownWebDAVScheme());
+ CPPUNIT_ASSERT(
+ INetURLObject(u"vnd.sun.star.webdav://example.org").isAnyKnownWebDAVScheme());
+ CPPUNIT_ASSERT(
+ INetURLObject(u"vnd.sun.star.webdavs://example.org").isAnyKnownWebDAVScheme());
+ CPPUNIT_ASSERT(
+ !INetURLObject(u"ftp://example.org").isAnyKnownWebDAVScheme());
+ CPPUNIT_ASSERT(
+ !INetURLObject(u"file://example.org").isAnyKnownWebDAVScheme());
+ CPPUNIT_ASSERT(
+ !INetURLObject(u"dav://example.org").isAnyKnownWebDAVScheme());
+ CPPUNIT_ASSERT(
+ !INetURLObject(u"davs://example.org").isAnyKnownWebDAVScheme());
+ CPPUNIT_ASSERT(
+ !INetURLObject(u"vnd.sun.star.pkg://example.org").isAnyKnownWebDAVScheme());
+ }
+
+ void testSetName() {
+ {
+ INetURLObject obj(u"file:///");
+ bool ok = obj.setName(u"foo");
+ CPPUNIT_ASSERT(!ok);
+ }
+ {
+ INetURLObject obj(u"file:///foo");
+ bool ok = obj.setName(u"bar");
+ CPPUNIT_ASSERT(ok);
+ CPPUNIT_ASSERT_EQUAL(
+ OUString("file:///bar"), obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+ {
+ INetURLObject obj(u"file:///foo/");
+ bool ok = obj.setName(u"bar");
+ CPPUNIT_ASSERT(ok);
+ CPPUNIT_ASSERT_EQUAL(
+ OUString("file:///bar/"), obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+ {
+ INetURLObject obj(u"file:///foo/bar");
+ bool ok = obj.setName(u"baz");
+ CPPUNIT_ASSERT(ok);
+ CPPUNIT_ASSERT_EQUAL(
+ OUString("file:///foo/baz"),
+ obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+ {
+ INetURLObject obj(u"file:///foo/bar/");
+ bool ok = obj.setName(u"baz");
+ CPPUNIT_ASSERT(ok);
+ CPPUNIT_ASSERT_EQUAL(
+ OUString("file:///foo/baz/"),
+ obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+ }
+
+ void testSetExtension() {
+ INetURLObject obj(u"file:///foo/bar.baz/");
+ bool ok = obj.setExtension(
+ u"other", INetURLObject::LAST_SEGMENT, false);
+ CPPUNIT_ASSERT(ok);
+ CPPUNIT_ASSERT_EQUAL(
+ OUString("file:///foo/bar.baz/.other"),
+ obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+
+ void testChangeScheme() {
+ INetURLObject obj(u"unknown://example.com/foo/bar");
+ CPPUNIT_ASSERT(!obj.HasError());
+ obj.changeScheme(INetProtocol::Http);
+ CPPUNIT_ASSERT_EQUAL(
+ OUString("http://example.com/foo/bar"),
+ obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ obj.changeScheme(INetProtocol::Https);
+ CPPUNIT_ASSERT_EQUAL(
+ OUString("https://example.com/foo/bar"),
+ obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ obj.changeScheme(INetProtocol::Ftp);
+ CPPUNIT_ASSERT_EQUAL(
+ OUString("ftp://example.com/foo/bar"),
+ obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+
+ void testTd146382() {
+ INetURLObject obj(u"file://share.allotropia.de@SSL/DavWWWRoot/remote.php");
+ CPPUNIT_ASSERT(!obj.HasError());
+ CPPUNIT_ASSERT_EQUAL(
+ OUString("file://share.allotropia.de@SSL/DavWWWRoot/remote.php"),
+ obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+
+ void testParseSmart()
+ {
+ {
+ // host:port must not be misinterpreted as scheme:path
+ INetURLObject obj(u"example.com:8080/foo", INetProtocol::Http);
+ CPPUNIT_ASSERT(!obj.HasError());
+ CPPUNIT_ASSERT_EQUAL(OUString("http://example.com:8080/foo"),
+ obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ CPPUNIT_ASSERT_EQUAL(INetProtocol::Http, obj.GetProtocol());
+ CPPUNIT_ASSERT_EQUAL(OUString("example.com"), obj.GetHost());
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(8080), obj.GetPort());
+ CPPUNIT_ASSERT_EQUAL(OUString("/foo"), obj.GetURLPath());
+ }
+ {
+ // port may only contain decimal digits, so this must be treated as unknown scheme
+ INetURLObject obj(u"example.com:80a0/foo", INetProtocol::Http);
+ CPPUNIT_ASSERT(!obj.HasError());
+ CPPUNIT_ASSERT_EQUAL(OUString("example.com:80a0/foo"),
+ obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ CPPUNIT_ASSERT_EQUAL(INetProtocol::Generic, obj.GetProtocol());
+ CPPUNIT_ASSERT(obj.isSchemeEqualTo(u"example.com"));
+ CPPUNIT_ASSERT_EQUAL(OUString(""), obj.GetHost());
+ CPPUNIT_ASSERT_EQUAL(OUString("80a0/foo"), obj.GetURLPath());
+ }
+ {
+ // Test Windows \\?\C:... long path scheme
+ INetURLObject base(u"file:///C:/foo"); // set up base path
+ bool bWasAbsolute = false;
+ INetURLObject obj
+ = base.smartRel2Abs(u"\\\\?\\D:\\bar\\baz.ext"_ustr, bWasAbsolute);
+ CPPUNIT_ASSERT(bWasAbsolute);
+ CPPUNIT_ASSERT_EQUAL(u"file:///D:/bar/baz.ext"_ustr,
+ obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+ {
+ // Test Windows \\?\UNC\Server... long path scheme
+ INetURLObject base(u"file://ServerFoo/fooShare"); // set up base path
+ bool bWasAbsolute = false;
+ INetURLObject obj = base.smartRel2Abs(
+ u"\\\\?\\UNC\\ServerBar\\barShare\\baz.ext"_ustr, bWasAbsolute);
+ CPPUNIT_ASSERT(bWasAbsolute);
+ CPPUNIT_ASSERT_EQUAL(u"file://ServerBar/barShare/baz.ext"_ustr,
+ obj.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+ }
+
+ // Change the following lines only, if you add, remove or rename
+ // member functions of the current class,
+ // because these macros are need by auto register mechanism.
+
+ CPPUNIT_TEST_SUITE( urlobjTest );
+ CPPUNIT_TEST( urlobjTest_001 );
+ CPPUNIT_TEST( urlobjTest_004 );
+ CPPUNIT_TEST( urlobjCmisTest );
+ CPPUNIT_TEST( urlobjTest_emptyPath );
+ CPPUNIT_TEST( urlobjTest_data );
+ CPPUNIT_TEST( urlobjTest_isSchemeEqualTo );
+ CPPUNIT_TEST( urlobjTest_isAnyKnownWebDAVScheme );
+ CPPUNIT_TEST( testSetName );
+ CPPUNIT_TEST( testSetExtension );
+ CPPUNIT_TEST( testChangeScheme );
+ CPPUNIT_TEST( testTd146382 );
+ CPPUNIT_TEST( testParseSmart );
+ CPPUNIT_TEST_SUITE_END( );
+ }; // class createPool
+
+
+ CPPUNIT_TEST_SUITE_REGISTRATION( urlobjTest );
+} // namespace rtl_random
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_xmlwalker.cxx b/tools/qa/cppunit/test_xmlwalker.cxx
new file mode 100644
index 0000000000..6df278c21e
--- /dev/null
+++ b/tools/qa/cppunit/test_xmlwalker.cxx
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <test/bootstrapfixture.hxx>
+#include <rtl/ustring.hxx>
+#include <tools/stream.hxx>
+#include <tools/XmlWalker.hxx>
+
+namespace
+{
+class XmlWalkerTest : public test::BootstrapFixture
+{
+ OUString maBasePath;
+
+public:
+ XmlWalkerTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ virtual void setUp() override { maBasePath = m_directories.getURLFromSrc(u"/tools/qa/data/"); }
+
+ void testReadXML();
+
+ CPPUNIT_TEST_SUITE(XmlWalkerTest);
+ CPPUNIT_TEST(testReadXML);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void XmlWalkerTest::testReadXML()
+{
+ OUString aXmlFilePath = maBasePath + "test.xml";
+
+ tools::XmlWalker aWalker;
+ SvFileStream aFileStream(aXmlFilePath, StreamMode::READ);
+ CPPUNIT_ASSERT(aWalker.open(&aFileStream));
+ CPPUNIT_ASSERT_EQUAL("root"_ostr, aWalker.name());
+ CPPUNIT_ASSERT_EQUAL("Hello World"_ostr, aWalker.attribute("root-attr"_ostr));
+
+ int nNumberOfChildNodes = 0;
+
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ if (aWalker.name() == "child")
+ {
+ nNumberOfChildNodes++;
+
+ CPPUNIT_ASSERT_EQUAL(OString(OString::number(nNumberOfChildNodes)),
+ aWalker.attribute("number"_ostr));
+
+ if (nNumberOfChildNodes == 1) // only the first node has the attribute
+ CPPUNIT_ASSERT_EQUAL("123"_ostr, aWalker.attribute("attribute"_ostr));
+ else
+ CPPUNIT_ASSERT_EQUAL(OString(), aWalker.attribute("attribute"_ostr));
+
+ aWalker.children();
+ while (aWalker.isValid())
+ {
+ if (aWalker.name() == "grandchild")
+ {
+ CPPUNIT_ASSERT_EQUAL("ABC"_ostr, aWalker.attribute("attribute1"_ostr));
+ CPPUNIT_ASSERT_EQUAL("CDE"_ostr, aWalker.attribute("attribute2"_ostr));
+ CPPUNIT_ASSERT_EQUAL("Content"_ostr, aWalker.content());
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+ }
+ else if (aWalker.name() == "with-namespace")
+ {
+ CPPUNIT_ASSERT_EQUAL("adobe:ns:meta/"_ostr, aWalker.namespaceHref());
+ CPPUNIT_ASSERT_EQUAL("xx"_ostr, aWalker.namespacePrefix());
+ }
+ aWalker.next();
+ }
+ aWalker.parent();
+
+ CPPUNIT_ASSERT_EQUAL(3, nNumberOfChildNodes);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(XmlWalkerTest);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_xmlwriter.cxx b/tools/qa/cppunit/test_xmlwriter.cxx
new file mode 100644
index 0000000000..eeb475030f
--- /dev/null
+++ b/tools/qa/cppunit/test_xmlwriter.cxx
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <test/bootstrapfixture.hxx>
+#include <tools/stream.hxx>
+#include <tools/XmlWriter.hxx>
+
+namespace
+{
+class XmlWriterTest : public test::BootstrapFixture
+{
+public:
+ XmlWriterTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ virtual void setUp() override {}
+
+ void testSimpleRoot();
+ void testSpecialChars();
+
+ CPPUNIT_TEST_SUITE(XmlWriterTest);
+ CPPUNIT_TEST(testSimpleRoot);
+ CPPUNIT_TEST(testSpecialChars);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void XmlWriterTest::testSimpleRoot()
+{
+ SvMemoryStream aMemoryStream;
+
+ tools::XmlWriter aWriter(&aMemoryStream);
+ aWriter.startDocument(0, false);
+ aWriter.startElement("test");
+ aWriter.endElement();
+ aWriter.endDocument();
+
+ aMemoryStream.Seek(0);
+ OString aString(static_cast<const char*>(aMemoryStream.GetData()), aMemoryStream.GetSize());
+ CPPUNIT_ASSERT_EQUAL("<test/>"_ostr, aString);
+}
+
+void XmlWriterTest::testSpecialChars()
+{
+ SvMemoryStream aMemoryStream;
+
+ tools::XmlWriter aWriter(&aMemoryStream);
+ aWriter.startDocument(0, false);
+ aWriter.startElement("test");
+ aWriter.content("<>"_ostr);
+ aWriter.endElement();
+ aWriter.endDocument();
+
+ aMemoryStream.Seek(0);
+ OString aString(static_cast<const char*>(aMemoryStream.GetData()), aMemoryStream.GetSize());
+ CPPUNIT_ASSERT_EQUAL("<test>&lt;&gt;</test>"_ostr, aString);
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(XmlWriterTest);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/qa/cppunit/test_zcodec.cxx b/tools/qa/cppunit/test_zcodec.cxx
new file mode 100644
index 0000000000..935a234d4f
--- /dev/null
+++ b/tools/qa/cppunit/test_zcodec.cxx
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <tools/zcodec.hxx>
+#include <tools/stream.hxx>
+#include <rtl/crc.h>
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/plugin/TestPlugIn.h>
+
+namespace
+{
+// Sample text for compression
+constexpr const char* DUMMY_TEXT
+ = "Lorem ipsum dolor sit amet, consectetur adipiscing elit,\n"
+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n"
+ "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
+ "commodo consequat.\n";
+constexpr auto DUMMY_SIZE = std::char_traits<char>::length(DUMMY_TEXT);
+
+class ZCodecTest : public CppUnit::TestFixture
+{
+private:
+ void testGzCompressDecompress();
+ void testMakeDummyFile();
+ void testZlibCompressDecompress();
+
+ CPPUNIT_TEST_SUITE(ZCodecTest);
+ CPPUNIT_TEST(testMakeDummyFile);
+ CPPUNIT_TEST(testGzCompressDecompress);
+ CPPUNIT_TEST(testZlibCompressDecompress);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+// Creates a stream from DUMMY_TEXT to compress and decompress
+std::unique_ptr<SvMemoryStream> makeDummyFile()
+{
+ SvMemoryStream* pRet = new SvMemoryStream();
+ pRet->WriteBytes(DUMMY_TEXT, DUMMY_SIZE);
+ return std::unique_ptr<SvMemoryStream>(pRet);
+}
+
+// Test that the stream generated from makeDummyFile is valid
+void ZCodecTest::testMakeDummyFile()
+{
+ auto pStream = makeDummyFile();
+ // Check for null
+ CPPUNIT_ASSERT_MESSAGE("pStream is null", pStream);
+ decltype(DUMMY_SIZE) size = pStream->GetSize();
+ // Check size
+ CPPUNIT_ASSERT_EQUAL(DUMMY_SIZE, size);
+}
+
+// Test that ZCodec::Compress and ZCodec::Decompress work for gzlib = true
+// Compares the CRC32 checksum of the initial stream and the compressed->decompressed stream
+void ZCodecTest::testGzCompressDecompress()
+{
+ auto pInitialStream = makeDummyFile();
+ SvMemoryStream pCompressedStream;
+ SvMemoryStream pDecompressedStream;
+ auto nInitialStreamCRC32 = rtl_crc32(0, pInitialStream->GetData(), pInitialStream->GetSize());
+ ZCodec aCodec;
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, true);
+ // Set compression metadata for compressing a GZ file
+ aCodec.SetCompressionMetadata("dummy.txt"_ostr, 0, nInitialStreamCRC32);
+ aCodec.Compress(*pInitialStream, pCompressedStream);
+ auto nCompressedSize
+ = aCodec.EndCompression(); // returns compressed size or -1 if compression failed
+ // Check that the compression succeeded
+ CPPUNIT_ASSERT_GREATER(static_cast<tools::Long>(0), nCompressedSize);
+ // No need to Seek(0) here because ZCodec::InitDecompress does that already for gz
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, true);
+ aCodec.Decompress(pCompressedStream, pDecompressedStream);
+ auto nDecompressedSize
+ = aCodec.EndCompression(); // returns decompressed size or -1 if decompression failed
+ // Check that the decompressed text size is equal to the original
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(DUMMY_SIZE), nDecompressedSize);
+ auto nCompressedDecompressedStreamCRC32
+ = rtl_crc32(0, pDecompressedStream.GetData(), pDecompressedStream.GetSize());
+ // Check that the initial and decompressed CRC32 checksums are the same -> gz (de)compression works
+ CPPUNIT_ASSERT_EQUAL(nInitialStreamCRC32, nCompressedDecompressedStreamCRC32);
+}
+
+// Test that ZCodec::Compress and ZCodec::Decompress work for gzlib = false
+// Compares the CRC32 checksum of the initial stream and the compressed->decompressed stream
+void ZCodecTest::testZlibCompressDecompress()
+{
+ auto pInitialStream = makeDummyFile();
+ SvMemoryStream pCompressedStream;
+ SvMemoryStream pDecompressedStream;
+ auto nInitialStreamCRC32 = rtl_crc32(0, pInitialStream->GetData(), pInitialStream->GetSize());
+ ZCodec aCodec;
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, false);
+ aCodec.Compress(*pInitialStream, pCompressedStream);
+ auto nCompressedSize
+ = aCodec.EndCompression(); // returns compressed size or -1 if compression failed
+ // Check that the compression succeeded
+ CPPUNIT_ASSERT_GREATER(static_cast<tools::Long>(0), nCompressedSize);
+ pCompressedStream.Seek(0);
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, false);
+ aCodec.Decompress(pCompressedStream, pDecompressedStream);
+ auto nDecompressedSize
+ = aCodec.EndCompression(); // returns decompressed size or -1 if decompression failed
+ // Check that the decompressed text size is equal to the original
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(DUMMY_SIZE), nDecompressedSize);
+ auto nCompressedDecompressedStreamCRC32
+ = rtl_crc32(0, pDecompressedStream.GetData(), pDecompressedStream.GetSize());
+ // Check that the initial and decompressed CRC32 checksums are the same -> zlib (de)compression works
+ CPPUNIT_ASSERT_EQUAL(nInitialStreamCRC32, nCompressedDecompressedStreamCRC32);
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ZCodecTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/qa/data/test.xml b/tools/qa/data/test.xml
new file mode 100644
index 0000000000..c2736e8bb6
--- /dev/null
+++ b/tools/qa/data/test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<root root-attr="Hello World">
+ <child number="1" attribute="123">
+ <grandchild attribute1="ABC" attribute2="CDE">Content</grandchild>
+ </child>
+ <child number="2">
+ </child>
+ <child number="3">
+ </child>
+ <xx:with-namespace xmlns:xx="adobe:ns:meta/">
+ </xx:with-namespace>
+</root>
diff --git a/tools/qa/data/testconfig.ini b/tools/qa/data/testconfig.ini
new file mode 100644
index 0000000000..6307532164
--- /dev/null
+++ b/tools/qa/data/testconfig.ini
@@ -0,0 +1,5 @@
+[TestGroup]
+testkey=testvalue
+
+[TestGroup2]
+testkey2=testvalue
diff --git a/tools/source/datetime/datetime.cxx b/tools/source/datetime/datetime.cxx
new file mode 100644
index 0000000000..6f9dea26c6
--- /dev/null
+++ b/tools/source/datetime/datetime.cxx
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <tools/datetime.hxx>
+#include <tools/duration.hxx>
+#include <rtl/math.hxx>
+#include <sal/log.hxx>
+
+#include <systemdatetime.hxx>
+
+DateTime::DateTime(DateTimeInitSystem)
+ : Date( Date::EMPTY )
+ , Time( Time::EMPTY )
+{
+ sal_Int32 nD = 0;
+ sal_Int64 nT = 0;
+ if ( GetSystemDateTime( &nD, &nT ) )
+ {
+ Date::operator=( Date( nD ) );
+ SetTime( nT );
+ }
+ else
+ Date::operator=( Date( 1, 1, 1900 ) ); // Time::nTime is already 0
+}
+
+DateTime::DateTime( const css::util::DateTime& rDateTime )
+ : Date( rDateTime.Day, rDateTime.Month, rDateTime.Year ),
+ Time( rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, rDateTime.NanoSeconds )
+{
+}
+
+DateTime& DateTime::operator =( const css::util::DateTime& rUDateTime )
+{
+ Date::operator=( Date( rUDateTime.Day, rUDateTime.Month, rUDateTime.Year));
+ Time::operator=( Time( rUDateTime));
+ return *this;
+}
+
+bool DateTime::IsBetween( const DateTime& rFrom, const DateTime& rTo ) const
+{
+ return (*this >= rFrom) && (*this <= rTo);
+}
+
+bool DateTime::operator >( const DateTime& rDateTime ) const
+{
+ return (Date::operator>( rDateTime )) ||
+ (Date::operator==( rDateTime ) && tools::Time::operator>( rDateTime ));
+}
+
+bool DateTime::operator <( const DateTime& rDateTime ) const
+{
+ return (Date::operator<( rDateTime )) ||
+ (Date::operator==( rDateTime ) && tools::Time::operator<( rDateTime ));
+}
+
+bool DateTime::operator >=( const DateTime& rDateTime ) const
+{
+ return (Date::operator>( rDateTime )) ||
+ (Date::operator==( rDateTime ) && tools::Time::operator>=( rDateTime ));
+}
+
+bool DateTime::operator <=( const DateTime& rDateTime ) const
+{
+ return (Date::operator<( rDateTime )) ||
+ (Date::operator==( rDateTime ) && tools::Time::operator<=( rDateTime ));
+}
+
+sal_Int64 DateTime::GetSecFromDateTime( const Date& rDate ) const
+{
+ if ( Date::operator<( rDate ) )
+ return 0;
+ else
+ {
+ sal_Int64 nSec = Date( *this ) - rDate;
+ nSec *= 24UL*60*60;
+ sal_Int64 nHour = GetHour();
+ sal_Int64 nMin = GetMin();
+ nSec += (nHour*3600)+(nMin*60)+GetSec();
+ return nSec;
+ }
+}
+
+void DateTime::NormalizeTimeRemainderAndApply( tools::Time& rTime )
+{
+ sal_uInt16 nHours = rTime.GetHour();
+ if ( rTime.GetTime() > 0 )
+ {
+ if (nHours >= 24)
+ {
+ AddDays( nHours / 24 );
+ nHours %= 24;
+ rTime.SetHour( nHours );
+ }
+ }
+ else if ( rTime.GetTime() != 0 )
+ {
+ if (nHours >= 24)
+ {
+ AddDays( -static_cast<sal_Int32>(nHours) / 24 );
+ nHours %= 24;
+ rTime.SetHour( nHours );
+ }
+ Date::operator--();
+ rTime = Time( 24, 0, 0 ) + rTime;
+ }
+ tools::Time::operator=( rTime );
+}
+
+DateTime& DateTime::operator +=( const tools::Time& rTime )
+{
+ tools::Time aTime = *this;
+ aTime += rTime;
+ NormalizeTimeRemainderAndApply(aTime);
+ return *this;
+}
+
+DateTime& DateTime::operator -=( const tools::Time& rTime )
+{
+ tools::Time aTime = *this;
+ aTime -= rTime;
+ NormalizeTimeRemainderAndApply(aTime);
+ return *this;
+}
+
+DateTime& DateTime::operator +=( const tools::Duration& rDuration )
+{
+ AddDays(rDuration.GetDays());
+ operator+=(rDuration.GetTime());
+ return *this;
+}
+
+DateTime operator +( const DateTime& rDateTime, sal_Int32 nDays )
+{
+ DateTime aDateTime( rDateTime );
+ aDateTime.AddDays( nDays );
+ return aDateTime;
+}
+
+DateTime operator -( const DateTime& rDateTime, sal_Int32 nDays )
+{
+ DateTime aDateTime( rDateTime );
+ aDateTime.AddDays( -nDays );
+ return aDateTime;
+}
+
+DateTime operator +( const DateTime& rDateTime, const tools::Time& rTime )
+{
+ DateTime aDateTime( rDateTime );
+ aDateTime += rTime;
+ return aDateTime;
+}
+
+DateTime operator -( const DateTime& rDateTime, const tools::Time& rTime )
+{
+ DateTime aDateTime( rDateTime );
+ aDateTime -= rTime;
+ return aDateTime;
+}
+
+DateTime operator +( const DateTime& rDateTime, const tools::Duration& rDuration )
+{
+ DateTime aDateTime(rDateTime);
+ aDateTime.AddDays( rDuration.GetDays());
+ aDateTime += rDuration.GetTime();
+ return aDateTime;
+}
+
+void DateTime::AddTime( double fTimeInDays )
+{
+ // Use Duration to diminish floating point accuracy errors.
+ tools::Duration aDuration(fTimeInDays);
+ operator+=(aDuration);
+}
+
+DateTime operator +( const DateTime& rDateTime, double fTimeInDays )
+{
+ DateTime aDateTime( rDateTime );
+ aDateTime.AddTime( fTimeInDays );
+ return aDateTime;
+}
+
+tools::Duration operator -( const DateTime& rDateTime1, const DateTime& rDateTime2 )
+{
+ return tools::Duration( rDateTime2, rDateTime1);
+}
+
+// static
+double DateTime::Sub( const DateTime& rDateTime1, const DateTime& rDateTime2 )
+{
+ if (static_cast<const tools::Time&>(rDateTime1) != static_cast<const tools::Time&>(rDateTime2))
+ {
+ // Use Duration to diminish floating point accuracy errors.
+ const tools::Duration aDuration( rDateTime2, rDateTime1);
+ return aDuration.GetInDays();
+ }
+ return static_cast<const Date&>(rDateTime1) - static_cast<const Date&>(rDateTime2);
+}
+
+void DateTime::GetWin32FileDateTime( sal_uInt32 & rLower, sal_uInt32 & rUpper ) const
+{
+ const sal_Int64 a100nPerSecond = SAL_CONST_INT64( 10000000 );
+ const sal_Int64 a100nPerDay = a100nPerSecond * sal_Int64( 60 * 60 * 24 );
+
+ // FILETIME is indirectly documented as uint64, see
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284.aspx
+ // mentioning the ULARGE_INTEGER structure.
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724280.aspx
+ // mentions that if FILETIME is not less than 0x8000000000000000 then the
+ // FileTimeToSystemTime function fails, which is another indicator.
+ // Unless there's evidence that FILETIME can represent a signed offset from
+ // 1601-01-01 truncate at 0. (reading part below in
+ // CreateFromWin32FileDateTime() would had to be adapted to signed as
+ // well).
+ sal_Int16 nYear = GetYear();
+ SAL_WARN_IF( nYear < 1601, "tools.datetime", "DateTime::GetWin32FileDateTime - year < 1601: " << nYear);
+
+ sal_Int64 aTime = (nYear < 1601 ? 0 : (
+ a100nPerDay * (*this - Date(1,1,1601)) +
+ GetNSFromTime()/100));
+
+ rLower = sal_uInt32( aTime % SAL_CONST_UINT64( 0x100000000 ) );
+ rUpper = sal_uInt32( aTime / SAL_CONST_UINT64( 0x100000000 ) );
+}
+
+DateTime DateTime::CreateFromWin32FileDateTime( sal_uInt32 rLower, sal_uInt32 rUpper )
+{
+ // (rUpper|rLower) = 100-nanosecond intervals since 1601-01-01 00:00
+ const sal_uInt64 a100nPerSecond = SAL_CONST_UINT64( 10000000 );
+ const sal_uInt64 a100nPerDay = a100nPerSecond * sal_uInt64( 60 * 60 * 24 );
+
+ sal_uInt64 aTime =
+ sal_uInt64( rUpper ) * SAL_CONST_UINT64( 0x100000000 ) +
+ sal_uInt64( rLower );
+
+ SAL_WARN_IF( static_cast<sal_Int64>(aTime) < 0, "tools.datetime",
+ "DateTime::CreateFromWin32FileDateTime - absurdly high value expected?");
+
+ sal_uInt64 nDays = aTime / a100nPerDay;
+
+ Date aDate(1,1,1601);
+ // (0xffffffffffffffff / a100nPerDay = 21350398) fits into sal_Int32
+ // (0x7fffffff = 2147483647)
+ aDate.AddDays(nDays);
+
+ SAL_WARN_IF( aDate - Date(1,1,1601) != static_cast<sal_Int32>(nDays), "tools.datetime",
+ "DateTime::CreateFromWin32FileDateTime - date truncated to max");
+
+ sal_uInt64 nNanos = (aTime - (nDays * a100nPerDay)) * 100;
+ return DateTime( aDate, tools::Time(
+ static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerHour) % sal_uInt64( 24 )),
+ static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerMinute) % sal_uInt64( 60 )),
+ static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerSec) % sal_uInt64( 60 )),
+ static_cast<sal_uInt64>( nNanos % tools::Time::nanoSecPerSec)));
+}
+
+DateTime DateTime::CreateFromUnixTime(const double fSecondsSinceEpoch)
+{
+ double fValue = fSecondsSinceEpoch / Time::secondPerDay;
+ const sal_Int32 nDays = static_cast <sal_Int32>(::rtl::math::approxFloor(fValue));
+
+ Date aDate (1, 1, 1970);
+ aDate.AddDays(nDays);
+ SAL_WARN_IF(aDate - Date(1, 1, 1970) != nDays, "tools.datetime",
+ "DateTime::CreateFromUnixTime - date truncated to max");
+
+ fValue -= nDays;
+
+ const sal_uInt64 nNanos = fValue * tools::Time::nanoSecPerDay;
+ return DateTime( aDate, tools::Time(
+ static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerHour) % sal_uInt64( 24 )),
+ static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerMinute) % sal_uInt64( 60 )),
+ static_cast<sal_uInt32>((nNanos / tools::Time::nanoSecPerSec) % sal_uInt64( 60 )),
+ static_cast<sal_uInt64>( nNanos % tools::Time::nanoSecPerSec)));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/datetime/datetimeutils.cxx b/tools/source/datetime/datetimeutils.cxx
new file mode 100644
index 0000000000..4c3b28d49d
--- /dev/null
+++ b/tools/source/datetime/datetimeutils.cxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <tools/datetimeutils.hxx>
+#include <rtl/strbuf.hxx>
+
+
+/// Append the number as 2-digit when less than 10.
+static void lcl_AppendTwoDigits( OStringBuffer &rBuffer, sal_Int32 nNum )
+{
+ if ( nNum < 0 || nNum > 99 )
+ {
+ rBuffer.append( "00" );
+ return;
+ }
+
+ if ( nNum < 10 )
+ rBuffer.append( '0' );
+
+ rBuffer.append( nNum );
+}
+
+OString DateTimeToOString( const DateTime& rDateTime )
+{
+ const DateTime& aInUTC( rDateTime );
+// HACK: this is correct according to the spec, but MSOffice believes everybody lives
+// in UTC+0 when reading it back
+// aInUTC.ConvertToUTC();
+
+ OStringBuffer aBuffer( 25 );
+ aBuffer.append( sal_Int32( aInUTC.GetYear() ) );
+ aBuffer.append( '-' );
+
+ lcl_AppendTwoDigits( aBuffer, aInUTC.GetMonth() );
+ aBuffer.append( '-' );
+
+ lcl_AppendTwoDigits( aBuffer, aInUTC.GetDay() );
+ aBuffer.append( 'T' );
+
+ lcl_AppendTwoDigits( aBuffer, aInUTC.GetHour() );
+ aBuffer.append( ':' );
+
+ lcl_AppendTwoDigits( aBuffer, aInUTC.GetMin() );
+ aBuffer.append( ':' );
+
+ lcl_AppendTwoDigits( aBuffer, aInUTC.GetSec() );
+ aBuffer.append( 'Z' ); // we are in UTC
+
+ return aBuffer.makeStringAndClear();
+}
+
+OString DateToOString( const Date& rDate )
+{
+ tools::Time aTime( tools::Time::EMPTY );
+ return DateTimeToOString( DateTime( rDate, aTime ) );
+}
+
+OString DateToDDMMYYYYOString( const Date& rDate )
+{
+ OStringBuffer aBuffer( 25 );
+ lcl_AppendTwoDigits( aBuffer, rDate.GetDay() );
+ aBuffer.append( '/' );
+
+ lcl_AppendTwoDigits( aBuffer, rDate.GetMonth() );
+ aBuffer.append( '/' );
+
+ aBuffer.append( sal_Int32( rDate.GetYear() ) );
+
+ return aBuffer.makeStringAndClear();
+}
+
+std::ostream& operator<<(std::ostream& os, const Date& rDate)
+{
+ os << rDate.GetYear() << "-" << rDate.GetMonth() << "-" << rDate.GetDay();
+ return os;
+}
diff --git a/tools/source/datetime/duration.cxx b/tools/source/datetime/duration.cxx
new file mode 100644
index 0000000000..a655f016a1
--- /dev/null
+++ b/tools/source/datetime/duration.cxx
@@ -0,0 +1,328 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <tools/duration.hxx>
+#include <tools/datetime.hxx>
+#include <rtl/math.hxx>
+#include <o3tl/safeint.hxx>
+#include <cmath>
+
+namespace tools
+{
+Duration::Duration(const ::DateTime& rStart, const ::DateTime& rEnd)
+ : mnDays(static_cast<const Date&>(rEnd) - static_cast<const Date&>(rStart))
+{
+ SetTimeDiff(rStart, rEnd);
+}
+
+Duration::Duration(const Time& rStart, const Time& rEnd)
+{
+ const sal_uInt16 nStartHour = rStart.GetHour();
+ const sal_uInt16 nEndHour = rEnd.GetHour();
+ if (nStartHour >= 24 || nEndHour >= 24)
+ {
+ Time aEnd(rEnd);
+ if (nEndHour >= 24)
+ {
+ mnDays = (nEndHour / 24) * (aEnd.GetTime() < 0 ? -1 : 1);
+ aEnd.SetHour(nEndHour % 24);
+ }
+ Time aStart(rStart);
+ if (nStartHour >= 24)
+ {
+ mnDays -= (nStartHour / 24) * (aStart.GetTime() < 0 ? -1 : 1);
+ aStart.SetHour(nStartHour % 24);
+ }
+ SetTimeDiff(aStart, aEnd);
+ }
+ else
+ {
+ SetTimeDiff(rStart, rEnd);
+ }
+}
+
+Duration::Duration(double fTimeInDays, sal_uInt64 nAccuracyEpsilonNanoseconds)
+{
+ assert(nAccuracyEpsilonNanoseconds <= Time::nanoSecPerSec - 1);
+ double fInt, fFrac;
+ if (fTimeInDays < 0.0)
+ {
+ fInt = ::rtl::math::approxCeil(fTimeInDays);
+ fFrac = fInt <= fTimeInDays ? 0.0 : fTimeInDays - fInt;
+ }
+ else
+ {
+ fInt = ::rtl::math::approxFloor(fTimeInDays);
+ fFrac = fInt >= fTimeInDays ? 0.0 : fTimeInDays - fInt;
+ }
+ mnDays = static_cast<sal_Int32>(fInt);
+ if (fFrac)
+ {
+ fFrac *= Time::nanoSecPerDay;
+ fFrac = ::rtl::math::approxFloor(fFrac);
+ sal_Int64 nNS = static_cast<sal_Int64>(fFrac);
+ const sal_Int64 nN = nNS % Time::nanoSecPerSec;
+ if (nN)
+ {
+ const sal_uInt64 nA = std::abs(nN);
+ if (nA <= nAccuracyEpsilonNanoseconds)
+ nNS -= (nNS < 0) ? -nN : nN;
+ else if (nA >= Time::nanoSecPerSec - nAccuracyEpsilonNanoseconds)
+ {
+ const sal_Int64 nD = Time::nanoSecPerSec - nA;
+ nNS += (nNS < 0) ? -nD : nD;
+ if (std::abs(nNS) >= Time::nanoSecPerDay)
+ {
+ mnDays += nNS / Time::nanoSecPerDay;
+ nNS %= Time::nanoSecPerDay;
+ }
+ }
+ }
+ maTime.MakeTimeFromNS(nNS);
+ assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (nNS < 0));
+ }
+}
+
+Duration::Duration(sal_Int32 nDays, const Time& rTime)
+ : mnDays(nDays)
+{
+ assert(nDays == 0 || rTime.GetTime() == 0 || (nDays < 0) == (rTime.GetTime() < 0));
+ Normalize(rTime.GetHour(), rTime.GetMin(), rTime.GetSec(), rTime.GetNanoSec(),
+ ((nDays < 0) || (rTime.GetTime() < 0)));
+}
+
+Duration::Duration(sal_Int32 nDays, sal_uInt32 nHours, sal_uInt32 nMinutes, sal_uInt32 nSeconds,
+ sal_uInt64 nNanoseconds)
+ : mnDays(nDays)
+{
+ Normalize(nHours, nMinutes, nSeconds, nNanoseconds, nDays < 0);
+}
+
+Duration::Duration(sal_Int32 nDays, sal_Int64 nTime)
+ : maTime(nTime)
+ , mnDays(nDays)
+{
+}
+
+void Duration::Normalize(sal_uInt64 nHours, sal_uInt64 nMinutes, sal_uInt64 nSeconds,
+ sal_uInt64 nNanoseconds, bool bNegative)
+{
+ if (nNanoseconds >= Time::nanoSecPerSec)
+ {
+ nSeconds += nNanoseconds / Time::nanoSecPerSec;
+ nNanoseconds %= Time::nanoSecPerSec;
+ }
+ if (nSeconds >= Time::secondPerMinute)
+ {
+ nMinutes += nSeconds / Time::secondPerMinute;
+ nSeconds %= Time::secondPerMinute;
+ }
+ if (nMinutes >= Time::minutePerHour)
+ {
+ nHours += nMinutes / Time::minutePerHour;
+ nMinutes %= Time::minutePerHour;
+ }
+ if (nHours >= Time::hourPerDay)
+ {
+ sal_Int64 nDiff = nHours / Time::hourPerDay;
+ nHours %= Time::hourPerDay;
+ bool bOverflow = false;
+ if (bNegative)
+ {
+ nDiff = -nDiff;
+ bOverflow = (nDiff < SAL_MIN_INT32);
+ bOverflow |= o3tl::checked_add(mnDays, static_cast<sal_Int32>(nDiff), mnDays);
+ if (bOverflow)
+ mnDays = SAL_MIN_INT32;
+ }
+ else
+ {
+ bOverflow = (nDiff > SAL_MAX_INT32);
+ bOverflow |= o3tl::checked_add(mnDays, static_cast<sal_Int32>(nDiff), mnDays);
+ if (bOverflow)
+ mnDays = SAL_MAX_INT32;
+ }
+ assert(!bOverflow);
+ if (bOverflow)
+ {
+ nHours = Time::hourPerDay - 1;
+ nMinutes = Time::minutePerHour - 1;
+ nSeconds = Time::secondPerMinute - 1;
+ nNanoseconds = Time::nanoSecPerSec - 1;
+ }
+ }
+ maTime = Time(nHours, nMinutes, nSeconds, nNanoseconds);
+ if (bNegative)
+ maTime = -maTime;
+ assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (maTime.GetTime() < 0));
+}
+
+void Duration::ApplyTime(sal_Int64 nNS)
+{
+ if (mnDays > 0 && nNS < 0)
+ {
+ --mnDays;
+ nNS = Time::nanoSecPerDay + nNS;
+ }
+ else if (mnDays < 0 && nNS > 0)
+ {
+ ++mnDays;
+ nNS = -Time::nanoSecPerDay + nNS;
+ }
+ maTime.MakeTimeFromNS(nNS);
+ assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (maTime.GetTime() < 0));
+}
+
+void Duration::SetTimeDiff(const Time& rStart, const Time& rEnd)
+{
+ const sal_Int64 nNS = rEnd.GetNSFromTime() - rStart.GetNSFromTime();
+ ApplyTime(nNS);
+}
+
+Duration Duration::operator-() const
+{
+ Duration aD(-mnDays, -maTime.GetTime());
+ return aD;
+}
+
+Duration& Duration::Add(const Duration& rDuration, bool& rbOverflow)
+{
+ rbOverflow = o3tl::checked_add(mnDays, rDuration.mnDays, mnDays);
+ // Duration is always normalized, time values >= 24h don't occur.
+ sal_Int64 nNS = maTime.GetNSFromTime() + rDuration.maTime.GetNSFromTime();
+ if (nNS < -Time::nanoSecPerDay)
+ {
+ rbOverflow |= o3tl::checked_sub(mnDays, sal_Int32(1), mnDays);
+ nNS += Time::nanoSecPerDay;
+ }
+ else if (nNS > Time::nanoSecPerDay)
+ {
+ rbOverflow |= o3tl::checked_add(mnDays, sal_Int32(1), mnDays);
+ nNS -= Time::nanoSecPerDay;
+ }
+ ApplyTime(nNS);
+ return *this;
+}
+
+Duration Duration::Mult(sal_Int32 nMult, bool& rbOverflow) const
+{
+ // First try a simple calculation in nanoseconds.
+ bool bBadNS = false;
+ sal_Int64 nNS;
+ sal_Int64 nDays;
+ if (o3tl::checked_multiply(static_cast<sal_Int64>(mnDays), static_cast<sal_Int64>(nMult), nDays)
+ || o3tl::checked_multiply(nDays, Time::nanoSecPerDay, nDays)
+ || o3tl::checked_multiply(maTime.GetNSFromTime(), static_cast<sal_Int64>(nMult), nNS)
+ || o3tl::checked_add(nDays, nNS, nNS))
+ {
+ bBadNS = rbOverflow = true;
+ }
+ else
+ {
+ const sal_Int64 nD = nNS / Time::nanoSecPerDay;
+ if (nD < SAL_MIN_INT32 || SAL_MAX_INT32 < nD)
+ rbOverflow = true;
+ else
+ {
+ rbOverflow = false;
+ nNS -= nD * Time::nanoSecPerDay;
+ Duration aD(static_cast<sal_Int32>(nD), 0);
+ aD.ApplyTime(nNS);
+ return aD;
+ }
+ }
+ if (bBadNS)
+ {
+ // Simple calculation in overall nanoseconds overflowed, try with
+ // individual components.
+ const sal_uInt64 nMult64 = (nMult < 0) ? -nMult : nMult;
+ do
+ {
+ sal_uInt64 nN;
+ if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetNanoSec()), nMult64, nN))
+ break;
+ sal_uInt64 nS;
+ if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetSec()), nMult64, nS))
+ break;
+ sal_uInt64 nM;
+ if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetMin()), nMult64, nM))
+ break;
+ sal_uInt64 nH;
+ if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetHour()), nMult64, nH))
+ break;
+ sal_uInt64 nD;
+ if (o3tl::checked_multiply(
+ mnDays < 0 ? static_cast<sal_uInt64>(-static_cast<sal_Int64>(mnDays))
+ : static_cast<sal_uInt64>(mnDays),
+ nMult64, nD))
+ break;
+ if (nN > Time::nanoSecPerSec)
+ {
+ const sal_uInt64 nC = nN / Time::nanoSecPerSec;
+ if (o3tl::checked_add(nS, nC, nS))
+ break;
+ nN -= nC * Time::nanoSecPerSec;
+ }
+ if (nS > Time::secondPerMinute)
+ {
+ const sal_uInt64 nC = nS / Time::secondPerMinute;
+ if (o3tl::checked_add(nM, nC, nM))
+ break;
+ nS -= nC * Time::secondPerMinute;
+ }
+ if (nM > Time::minutePerHour)
+ {
+ const sal_uInt64 nC = nM / Time::minutePerHour;
+ if (o3tl::checked_add(nH, nC, nH))
+ break;
+ nM -= nC * Time::minutePerHour;
+ }
+ if (nH > Time::hourPerDay)
+ {
+ const sal_uInt64 nC = nH / Time::hourPerDay;
+ if (o3tl::checked_add(nD, nC, nD))
+ break;
+ nH -= nC * Time::hourPerDay;
+ }
+ if (IsNegative() ? (static_cast<sal_uInt64>(SAL_MAX_INT32) + 1) < nD
+ || -static_cast<sal_Int64>(nD) < SAL_MIN_INT32
+ : SAL_MAX_INT32 < nD)
+ break;
+
+ rbOverflow = false;
+ Time aTime(nH, nM, nS, nN);
+ if (IsNegative() == (nMult < 0))
+ {
+ Duration aD(nD, aTime.GetTime());
+ return aD;
+ }
+ else
+ {
+ Duration aD(-static_cast<sal_Int64>(nD), -aTime.GetTime());
+ return aD;
+ }
+ } while (false);
+ }
+ assert(rbOverflow);
+ if (IsNegative() == (nMult < 0))
+ {
+ Duration aD(SAL_MAX_INT32, 0);
+ aD.ApplyTime(Time::nanoSecPerDay - 1);
+ return aD;
+ }
+ else
+ {
+ Duration aD(SAL_MIN_INT32, 0);
+ aD.ApplyTime(-(Time::nanoSecPerDay - 1));
+ return aD;
+ }
+}
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/source/datetime/systemdatetime.cxx b/tools/source/datetime/systemdatetime.cxx
new file mode 100644
index 0000000000..2115f4b620
--- /dev/null
+++ b/tools/source/datetime/systemdatetime.cxx
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#if defined(_WIN32)
+#if !defined WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#elif defined UNX
+#include <sys/time.h>
+#endif
+
+#include <time.h>
+#ifdef __MACH__
+#include <mach/clock.h>
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#endif
+
+#include <osl/diagnose.h>
+#include <systemdatetime.hxx>
+
+namespace
+{
+constexpr sal_Int32 ConvertYMDToInt(sal_Int32 nYear, sal_Int32 nMonth, sal_Int32 nDay)
+{
+ return (nYear * 10000) + (nMonth * 100) + nDay;
+}
+
+constexpr sal_Int64 ConvertHMSnToInt(sal_Int64 nHour, sal_Int64 nMin, sal_Int64 nSec,
+ sal_Int64 nNanoSec)
+{
+ return (nHour * HOUR_MASK) + (nMin * MIN_MASK) + (nSec * SEC_MASK) + nNanoSec;
+}
+}
+
+bool GetSystemDateTime(sal_Int32* pDate, sal_Int64* pTime)
+{
+#if defined(_WIN32)
+ SYSTEMTIME aDateTime;
+ GetLocalTime(&aDateTime);
+
+ if (pDate)
+ *pDate = ConvertYMDToInt(static_cast<sal_Int32>(aDateTime.wYear),
+ static_cast<sal_Int32>(aDateTime.wMonth),
+ static_cast<sal_Int32>(aDateTime.wDay));
+ if (pTime)
+ *pTime = ConvertHMSnToInt(aDateTime.wHour, aDateTime.wMinute, aDateTime.wSecond,
+ aDateTime.wMilliseconds * 1000000);
+
+ return true;
+#else
+ struct timespec tsTime;
+#if defined(__MACH__)
+ // macOS does not have clock_gettime, use clock_get_time
+ clock_serv_t cclock;
+ mach_timespec_t mts;
+ host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+ clock_get_time(cclock, &mts);
+ mach_port_deallocate(mach_task_self(), cclock);
+ tsTime.tv_sec = mts.tv_sec;
+ tsTime.tv_nsec = mts.tv_nsec;
+#else
+ // CLOCK_REALTIME should be supported
+ // on any modern Unix, but be extra cautious
+ if (clock_gettime(CLOCK_REALTIME, &tsTime) != 0)
+ {
+ struct timeval tvTime;
+ OSL_VERIFY(gettimeofday(&tvTime, nullptr) != 0);
+ tsTime.tv_sec = tvTime.tv_sec;
+ tsTime.tv_nsec = tvTime.tv_usec * 1000;
+ }
+#endif
+
+ struct tm aTime;
+ time_t nTmpTime = tsTime.tv_sec;
+ if (localtime_r(&nTmpTime, &aTime))
+ {
+ if (pDate)
+ *pDate = ConvertYMDToInt(static_cast<sal_Int32>(aTime.tm_year + 1900),
+ static_cast<sal_Int32>(aTime.tm_mon + 1),
+ static_cast<sal_Int32>(aTime.tm_mday));
+ if (pTime)
+ *pTime = ConvertHMSnToInt(aTime.tm_hour, aTime.tm_min, aTime.tm_sec, tsTime.tv_nsec);
+ return true;
+ }
+
+ return false;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/source/datetime/tdate.cxx b/tools/source/datetime/tdate.cxx
new file mode 100644
index 0000000000..e20add4303
--- /dev/null
+++ b/tools/source/datetime/tdate.cxx
@@ -0,0 +1,412 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <tools/date.hxx>
+#include <sal/log.hxx>
+#include <com/sun/star/util/DateTime.hpp>
+
+#include <systemdatetime.hxx>
+#include <comphelper/date.hxx>
+
+namespace
+{
+
+const sal_Int16 kYearMax = SAL_MAX_INT16;
+const sal_Int16 kYearMin = SAL_MIN_INT16;
+
+}
+
+void Date::setDateFromDMY( sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear )
+{
+ // don't warn about 0/0/0, commonly used as a default-value/no-value
+ SAL_WARN_IF( nYear == 0 && !(nYear == 0 && nMonth == 0 && nDay == 0),
+ "tools.datetime", "Date::setDateFromDMY - sure about 0 year? It's not in the calendar.");
+ assert( nMonth < 100 && "nMonth % 100 not representable" );
+ assert( nDay < 100 && "nDay % 100 not representable" );
+ if (nYear < 0)
+ mnDate =
+ (static_cast<sal_Int32>( nYear ) * 10000) -
+ (static_cast<sal_Int32>( nMonth % 100 ) * 100) -
+ static_cast<sal_Int32>( nDay % 100 );
+ else
+ mnDate =
+ (static_cast<sal_Int32>( nYear ) * 10000) +
+ (static_cast<sal_Int32>( nMonth % 100 ) * 100) +
+ static_cast<sal_Int32>( nDay % 100 );
+}
+
+void Date::SetDate( sal_Int32 nNewDate )
+{
+ assert( ((nNewDate / 10000) != 0) && "you don't want to set a 0 year, do you?" );
+ mnDate = nNewDate;
+}
+
+// static
+sal_uInt16 Date::GetDaysInMonth( sal_uInt16 nMonth, sal_Int16 nYear )
+{
+ SAL_WARN_IF( nMonth < 1 || 12 < nMonth, "tools.datetime", "Date::GetDaysInMonth - nMonth out of bounds " << nMonth);
+ if (nMonth < 1)
+ nMonth = 1;
+ else if (12 < nMonth)
+ nMonth = 12;
+ return comphelper::date::getDaysInMonth( nMonth, nYear);
+}
+
+sal_Int32 Date::GetAsNormalizedDays() const
+{
+ // This is a very common datum we often calculate from.
+ if (mnDate == 18991230) // 1899-12-30
+ {
+#ifndef NDEBUG
+ static sal_Int32 nDays = DateToDays( GetDay(), GetMonth(), GetYear());
+ assert(nDays == 693594);
+#endif
+ return 693594;
+ }
+ // Not calling comphelper::date::convertDateToDaysNormalizing() here just
+ // avoids a second check on null-date handling like above.
+ sal_uInt16 nDay = GetDay();
+ sal_uInt16 nMonth = GetMonth();
+ sal_Int16 nYear = GetYear();
+ comphelper::date::normalize( nDay, nMonth, nYear);
+ return comphelper::date::convertDateToDays( nDay, nMonth, nYear);
+}
+
+sal_Int32 Date::DateToDays( sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear )
+{
+ return comphelper::date::convertDateToDaysNormalizing( nDay, nMonth, nYear);
+}
+
+static Date lcl_DaysToDate( sal_Int32 nDays )
+{
+ sal_uInt16 nDay;
+ sal_uInt16 nMonth;
+ sal_Int16 nYear;
+ comphelper::date::convertDaysToDate( nDays, nDay, nMonth, nYear);
+ return Date( nDay, nMonth, nYear);
+}
+
+Date::Date( DateInitSystem )
+{
+ if ( !GetSystemDateTime( &mnDate, nullptr ) )
+ setDateFromDMY( 1, 1, 1900 );
+}
+
+Date::Date( const css::util::DateTime& rDateTime )
+{
+ setDateFromDMY( rDateTime.Day, rDateTime.Month, rDateTime.Year );
+}
+
+void Date::SetDay( sal_uInt16 nNewDay )
+{
+ setDateFromDMY( nNewDay, GetMonth(), GetYear() );
+}
+
+void Date::SetMonth( sal_uInt16 nNewMonth )
+{
+ setDateFromDMY( GetDay(), nNewMonth, GetYear() );
+}
+
+void Date::SetYear( sal_Int16 nNewYear )
+{
+ assert( nNewYear != 0 );
+ setDateFromDMY( GetDay(), GetMonth(), nNewYear );
+}
+
+void Date::AddYears( sal_Int16 nAddYears )
+{
+ sal_Int16 nYear = GetYear();
+ if (nYear < 0)
+ {
+ if (nAddYears < 0)
+ {
+ if (nYear < kYearMin - nAddYears)
+ nYear = kYearMin;
+ else
+ nYear += nAddYears;
+ }
+ else
+ {
+ nYear += nAddYears;
+ if (nYear == 0)
+ nYear = 1;
+ }
+ }
+ else
+ {
+ if (nAddYears > 0)
+ {
+ if (kYearMax - nAddYears < nYear)
+ nYear = kYearMax;
+ else
+ nYear += nAddYears;
+ }
+ else
+ {
+ nYear += nAddYears;
+ if (nYear == 0)
+ nYear = -1;
+ }
+ }
+
+ SetYear( nYear );
+ if (GetMonth() == 2 && GetDay() == 29 && !comphelper::date::isLeapYear( nYear))
+ SetDay(28);
+}
+
+void Date::AddMonths( sal_Int32 nAddMonths )
+{
+ sal_Int32 nMonths = GetMonth() + nAddMonths;
+ sal_Int32 nNewMonth = nMonths % 12;
+ sal_Int32 nYear = GetYear() + nMonths / 12;
+ if( nMonths <= 0 || nNewMonth == 0 )
+ --nYear;
+ if( nNewMonth <= 0 )
+ nNewMonth += 12;
+ if (nYear == 0)
+ nYear = (nAddMonths < 0 ? -1 : 1);
+ else if (nYear < kYearMin)
+ nYear = kYearMin;
+ else if (nYear > kYearMax)
+ nYear = kYearMax;
+ SetMonth( static_cast<sal_uInt16>(nNewMonth) );
+ SetYear( static_cast<sal_Int16>(nYear) );
+ Normalize();
+}
+
+DayOfWeek Date::GetDayOfWeek() const
+{
+ return static_cast<DayOfWeek>((GetAsNormalizedDays()-1) % 7);
+}
+
+sal_uInt16 Date::GetDayOfYear() const
+{
+ sal_uInt16 nDay = GetDay();
+ sal_uInt16 nMonth = GetMonth();
+ sal_Int16 nYear = GetYear();
+ Normalize( nDay, nMonth, nYear);
+
+ for( sal_uInt16 i = 1; i < nMonth; i++ )
+ nDay += comphelper::date::getDaysInMonth( i, nYear );
+ return nDay;
+}
+
+sal_uInt16 Date::GetWeekOfYear( DayOfWeek eStartDay,
+ sal_Int16 nMinimumNumberOfDaysInWeek ) const
+{
+ short nWeek;
+ short n1WDay = static_cast<short>(Date( 1, 1, GetYear() ).GetDayOfWeek());
+ short nDayOfYear = static_cast<short>(GetDayOfYear());
+
+ // weekdays start at 0, thus decrement one
+ nDayOfYear--;
+ // account for StartDay
+ n1WDay = (n1WDay+(7-static_cast<short>(eStartDay))) % 7;
+
+ if (nMinimumNumberOfDaysInWeek < 1 || 7 < nMinimumNumberOfDaysInWeek)
+ {
+ SAL_WARN( "tools.datetime", "Date::GetWeekOfYear: invalid nMinimumNumberOfDaysInWeek" );
+ nMinimumNumberOfDaysInWeek = 4;
+ }
+
+ if ( nMinimumNumberOfDaysInWeek == 1 )
+ {
+ nWeek = ((n1WDay+nDayOfYear)/7) + 1;
+ // Set to 53rd week only if we're not in the
+ // first week of the new year
+ if ( nWeek == 54 )
+ nWeek = 1;
+ else if ( nWeek == 53 )
+ {
+ short nDaysInYear = static_cast<short>(GetDaysInYear());
+ short nDaysNextYear = static_cast<short>(Date( 1, 1, GetNextYear() ).GetDayOfWeek());
+ nDaysNextYear = (nDaysNextYear+(7-static_cast<short>(eStartDay))) % 7;
+ if ( nDayOfYear > (nDaysInYear-nDaysNextYear-1) )
+ nWeek = 1;
+ }
+ }
+ else if ( nMinimumNumberOfDaysInWeek == 7 )
+ {
+ nWeek = ((n1WDay+nDayOfYear)/7);
+ // First week of a year is equal to the last week of the previous year
+ if ( nWeek == 0 )
+ {
+ Date aLastDatePrevYear( 31, 12, GetPrevYear() );
+ nWeek = aLastDatePrevYear.GetWeekOfYear( eStartDay, nMinimumNumberOfDaysInWeek );
+ }
+ }
+ else // ( nMinimumNumberOfDaysInWeek == something_else, commentary examples for 4 )
+ {
+ // x_monday - thursday
+ if ( n1WDay < nMinimumNumberOfDaysInWeek )
+ nWeek = 1;
+ // Friday
+ else if ( n1WDay == nMinimumNumberOfDaysInWeek )
+ nWeek = 53;
+ // Saturday
+ else if ( n1WDay == nMinimumNumberOfDaysInWeek + 1 )
+ {
+ // Year after leap year
+ if ( Date( 1, 1, GetPrevYear() ).IsLeapYear() )
+ nWeek = 53;
+ else
+ nWeek = 52;
+ }
+ // Sunday
+ else
+ nWeek = 52;
+
+ if ( (nWeek == 1) || (nDayOfYear + n1WDay > 6) )
+ {
+ if ( nWeek == 1 )
+ nWeek += (nDayOfYear + n1WDay) / 7;
+ else
+ nWeek = (nDayOfYear + n1WDay) / 7;
+ if ( nWeek == 53 )
+ {
+ // next x_Sunday == first x_Sunday in the new year
+ // == still the same week!
+ sal_Int32 nTempDays = GetAsNormalizedDays();
+
+ nTempDays += 6 - (GetDayOfWeek()+(7-static_cast<short>(eStartDay))) % 7;
+ nWeek = lcl_DaysToDate( nTempDays ).GetWeekOfYear( eStartDay, nMinimumNumberOfDaysInWeek );
+ }
+ }
+ }
+
+ return static_cast<sal_uInt16>(nWeek);
+}
+
+sal_uInt16 Date::GetDaysInMonth() const
+{
+ sal_uInt16 nDay = GetDay();
+ sal_uInt16 nMonth = GetMonth();
+ sal_Int16 nYear = GetYear();
+ Normalize( nDay, nMonth, nYear);
+
+ return comphelper::date::getDaysInMonth( nMonth, nYear );
+}
+
+bool Date::IsLeapYear() const
+{
+ sal_Int16 nYear = GetYear();
+ return comphelper::date::isLeapYear( nYear );
+}
+
+bool Date::IsValidAndGregorian() const
+{
+ sal_uInt16 nDay = GetDay();
+ sal_uInt16 nMonth = GetMonth();
+ sal_Int16 nYear = GetYear();
+
+ if ( !nMonth || (nMonth > 12) )
+ return false;
+ if ( !nDay || (nDay > comphelper::date::getDaysInMonth( nMonth, nYear )) )
+ return false;
+ else if ( nYear <= 1582 )
+ {
+ if ( nYear < 1582 )
+ return false;
+ else if ( nMonth < 10 )
+ return false;
+ else if ( (nMonth == 10) && (nDay < 15) )
+ return false;
+ }
+
+ return true;
+}
+
+bool Date::IsValidDate() const
+{
+ return comphelper::date::isValidDate( GetDay(), GetMonth(), GetYear());
+}
+
+//static
+bool Date::IsValidDate( sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear )
+{
+ return comphelper::date::isValidDate( nDay, nMonth, nYear);
+}
+
+bool Date::IsEndOfMonth() const
+{
+ return IsEndOfMonth(GetDay(), GetMonth(), GetYear());
+}
+
+//static
+bool Date::IsEndOfMonth(sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear)
+{
+ return comphelper::date::isValidDate(nDay, nMonth, nYear)
+ && comphelper::date::getDaysInMonth(nMonth, nYear) == nDay;
+}
+
+void Date::Normalize()
+{
+ sal_uInt16 nDay = GetDay();
+ sal_uInt16 nMonth = GetMonth();
+ sal_Int16 nYear = GetYear();
+
+ if (!Normalize( nDay, nMonth, nYear))
+ return;
+
+ setDateFromDMY( nDay, nMonth, nYear );
+}
+
+//static
+bool Date::Normalize( sal_uInt16 & rDay, sal_uInt16 & rMonth, sal_Int16 & rYear )
+{
+ return comphelper::date::normalize( rDay, rMonth, rYear);
+}
+
+void Date::AddDays( sal_Int32 nDays )
+{
+ if (nDays != 0)
+ *this = lcl_DaysToDate( GetAsNormalizedDays() + nDays );
+}
+
+Date& Date::operator ++()
+{
+ *this = lcl_DaysToDate( GetAsNormalizedDays() + 1 );
+ return *this;
+}
+
+Date& Date::operator --()
+{
+ *this = lcl_DaysToDate( GetAsNormalizedDays() - 1 );
+ return *this;
+}
+
+Date operator +( const Date& rDate, sal_Int32 nDays )
+{
+ Date aDate( rDate );
+ aDate.AddDays( nDays );
+ return aDate;
+}
+
+Date operator -( const Date& rDate, sal_Int32 nDays )
+{
+ Date aDate( rDate );
+ aDate.AddDays( -nDays );
+ return aDate;
+}
+
+sal_Int32 operator -( const Date& rDate1, const Date& rDate2 )
+{
+ return rDate1.GetAsNormalizedDays() - rDate2.GetAsNormalizedDays();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/datetime/ttime.cxx b/tools/source/datetime/ttime.cxx
new file mode 100644
index 0000000000..fcfa2e080e
--- /dev/null
+++ b/tools/source/datetime/ttime.cxx
@@ -0,0 +1,506 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <algorithm>
+
+#if defined(_WIN32)
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <mmsystem.h>
+#elif defined UNX
+#include <sys/time.h>
+#include <unistd.h>
+#endif
+
+#include <time.h>
+#ifdef __MACH__
+#include <mach/clock.h>
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#endif
+
+#include <rtl/math.hxx>
+#include <tools/time.hxx>
+#include <com/sun/star/util/DateTime.hpp>
+
+#include <systemdatetime.hxx>
+
+#if defined(__sun) && defined(__GNUC__)
+extern long altzone;
+#endif
+
+namespace {
+
+ const sal_Int64 nanoSecInSec = 1000000000;
+ const sal_Int16 secInMin = 60;
+ const sal_Int16 minInHour = 60;
+
+ sal_Int64 TimeToNanoSec( const tools::Time& rTime )
+ {
+ short nSign = (rTime.GetTime() >= 0) ? +1 : -1;
+ sal_Int32 nHour = rTime.GetHour();
+ sal_Int32 nMin = rTime.GetMin();
+ sal_Int32 nSec = rTime.GetSec();
+ sal_Int32 nNanoSec = rTime.GetNanoSec();
+
+ sal_Int64 nRet = nNanoSec;
+ nRet += nSec * nanoSecInSec;
+ nRet += nMin * secInMin * nanoSecInSec;
+ nRet += nHour * minInHour * secInMin * nanoSecInSec;
+
+ return (nRet * nSign);
+ }
+
+ tools::Time NanoSecToTime( sal_Int64 nNanoSec )
+ {
+ short nSign;
+ if ( nNanoSec < 0 )
+ {
+ nNanoSec *= -1;
+ nSign = -1;
+ }
+ else
+ nSign = 1;
+
+ tools::Time aTime( 0, 0, 0, nNanoSec );
+ aTime.SetTime( aTime.GetTime() * nSign );
+ return aTime;
+ }
+
+} // anonymous namespace
+
+namespace tools {
+
+Time::Time( TimeInitSystem )
+{
+ if ( !GetSystemDateTime( nullptr, &nTime ) )
+ nTime = 0;
+}
+
+Time::Time( const tools::Time& rTime )
+{
+ nTime = rTime.nTime;
+}
+
+Time::Time( sal_uInt32 nHour, sal_uInt32 nMin, sal_uInt32 nSec, sal_uInt64 nNanoSec )
+{
+ init(nHour, nMin, nSec, nNanoSec);
+}
+Time::Time( const css::util::Time &_rTime )
+{
+ init(_rTime.Hours, _rTime.Minutes, _rTime.Seconds, _rTime.NanoSeconds);
+}
+Time::Time( const css::util::DateTime &_rDateTime )
+{
+ init(_rDateTime.Hours, _rDateTime.Minutes, _rDateTime.Seconds, _rDateTime.NanoSeconds);
+}
+
+void tools::Time::init( sal_uInt32 nHour, sal_uInt32 nMin, sal_uInt32 nSec, sal_uInt64 nNanoSec )
+{
+ // normalize time
+ nSec += nNanoSec / nanoSecInSec;
+ nNanoSec %= nanoSecInSec;
+ nMin += nSec / secInMin;
+ nSec %= secInMin;
+ nHour += nMin / minInHour;
+ nMin %= minInHour;
+
+ // 922337 * HOUR_MASK = 9223370000000000000 largest possible value, 922338
+ // would be -9223364073709551616.
+ assert(HOUR_MASK * nHour >= 0 && "use tools::Duration with days instead!");
+ if (HOUR_MASK * nHour < 0)
+ nHour = 922337;
+
+ // But as is, GetHour() retrieves only sal_uInt16. Though retrieving in
+ // nanoseconds or milliseconds might be possible this is all crap.
+ assert(nHour <= SAL_MAX_UINT16 && "use tools::Duration with days instead!");
+ if (nHour > SAL_MAX_UINT16)
+ nHour = SAL_MAX_UINT16;
+
+ // construct time
+ nTime = nNanoSec +
+ nSec * SEC_MASK +
+ nMin * MIN_MASK +
+ nHour * HOUR_MASK;
+}
+
+void tools::Time::SetHour( sal_uInt16 nNewHour )
+{
+ short nSign = (nTime >= 0) ? +1 : -1;
+ sal_Int32 nMin = GetMin();
+ sal_Int32 nSec = GetSec();
+ sal_Int32 nNanoSec = GetNanoSec();
+
+ nTime = nSign *
+ ( nNanoSec +
+ nSec * SEC_MASK +
+ nMin * MIN_MASK +
+ nNewHour * HOUR_MASK );
+}
+
+void tools::Time::SetMin( sal_uInt16 nNewMin )
+{
+ short nSign = (nTime >= 0) ? +1 : -1;
+ sal_Int32 nHour = GetHour();
+ sal_Int32 nSec = GetSec();
+ sal_Int32 nNanoSec = GetNanoSec();
+
+ // no overflow
+ nNewMin = nNewMin % minInHour;
+
+ nTime = nSign *
+ ( nNanoSec +
+ nSec * SEC_MASK +
+ nNewMin * MIN_MASK +
+ nHour * HOUR_MASK );
+}
+
+void tools::Time::SetSec( sal_uInt16 nNewSec )
+{
+ short nSign = (nTime >= 0) ? +1 : -1;
+ sal_Int32 nHour = GetHour();
+ sal_Int32 nMin = GetMin();
+ sal_Int32 nNanoSec = GetNanoSec();
+
+ // no overflow
+ nNewSec = nNewSec % secInMin;
+
+ nTime = nSign *
+ ( nNanoSec +
+ nNewSec * SEC_MASK +
+ nMin * MIN_MASK +
+ nHour * HOUR_MASK );
+}
+
+void tools::Time::SetNanoSec( sal_uInt32 nNewNanoSec )
+{
+ short nSign = (nTime >= 0) ? +1 : -1;
+ sal_Int32 nHour = GetHour();
+ sal_Int32 nMin = GetMin();
+ sal_Int32 nSec = GetSec();
+
+ // no overflow
+ nNewNanoSec = nNewNanoSec % nanoSecInSec;
+
+ nTime = nSign *
+ ( nNewNanoSec +
+ nSec * SEC_MASK +
+ nMin * MIN_MASK +
+ nHour * HOUR_MASK );
+}
+
+sal_Int64 tools::Time::GetNSFromTime() const
+{
+ short nSign = (nTime >= 0) ? +1 : -1;
+ sal_Int32 nHour = GetHour();
+ sal_Int32 nMin = GetMin();
+ sal_Int32 nSec = GetSec();
+ sal_Int32 nNanoSec = GetNanoSec();
+
+ return nSign *
+ ( nNanoSec +
+ nSec * nanoSecInSec +
+ nMin * (secInMin * nanoSecInSec) +
+ nHour * (minInHour * secInMin * nanoSecInSec) );
+}
+
+void tools::Time::MakeTimeFromNS( sal_Int64 nNS )
+{
+ short nSign;
+ if ( nNS < 0 )
+ {
+ nNS *= -1;
+ nSign = -1;
+ }
+ else
+ nSign = 1;
+
+ // avoid overflow when sal_uIntPtr is 32 bits
+ tools::Time aTime( 0, 0, nNS/nanoSecInSec, nNS % nanoSecInSec );
+ SetTime( aTime.GetTime() * nSign );
+}
+
+sal_Int32 tools::Time::GetMSFromTime() const
+{
+ short nSign = (nTime >= 0) ? +1 : -1;
+ sal_Int32 nHour = GetHour();
+ sal_Int32 nMin = GetMin();
+ sal_Int32 nSec = GetSec();
+ sal_Int32 nNanoSec = GetNanoSec();
+
+ return nSign *
+ ( nNanoSec/1000000 +
+ nSec * 1000 +
+ nMin * 60000 +
+ nHour * 3600000 );
+}
+
+void tools::Time::MakeTimeFromMS( sal_Int32 nMS )
+{
+ short nSign;
+ if ( nMS < 0 )
+ {
+ nMS *= -1;
+ nSign = -1;
+ }
+ else
+ nSign = 1;
+
+ // avoid overflow when sal_uIntPtr is 32 bits
+ tools::Time aTime( 0, 0, nMS/1000, (nMS % 1000) * 1000000 );
+ SetTime( aTime.GetTime() * nSign );
+}
+
+double tools::Time::GetTimeInDays() const
+{
+ short nSign = (nTime >= 0) ? +1 : -1;
+ double nHour = GetHour();
+ double nMin = GetMin();
+ double nSec = GetSec();
+ double nNanoSec = GetNanoSec();
+
+ return (nHour + (nMin / 60) + (nSec / (minInHour * secInMin)) + (nNanoSec / (minInHour * secInMin * nanoSecInSec))) / 24 * nSign;
+}
+
+// static
+void tools::Time::GetClock( double fTimeInDays,
+ sal_uInt16& nHour, sal_uInt16& nMinute, sal_uInt16& nSecond,
+ double& fFractionOfSecond, int nFractionDecimals )
+{
+ const double fTime = fTimeInDays - rtl::math::approxFloor(fTimeInDays); // date part absent
+
+ // If 0 then full day (or no day), shortcut.
+ // If < 0 then approxFloor() effectively returned the ceiling (note this
+ // also holds for negative fTimeInDays values) because of a near identical
+ // value, shortcut this to a full day as well.
+ // If >= 1.0 (actually == 1.0) then fTimeInDays is a negative small value
+ // not significant for a representable time and approxFloor() returned -1,
+ // shortcut to 0:0:0, otherwise it would become 24:0:0.
+ if (fTime <= 0.0 || fTime >= 1.0)
+ {
+ nHour = nMinute = nSecond = 0;
+ fFractionOfSecond = 0.0;
+ return;
+ }
+
+ // In seconds, including milli and nano.
+ const double fRawSeconds = fTime * tools::Time::secondPerDay;
+
+ // Round to nanoseconds most, which is the highest resolution this could be
+ // influenced by, but if the original value included a date round to at
+ // most 14 significant digits (including adding 4 for *86400), otherwise we
+ // might end up with a fake precision of h:m:s.999999986 which in fact
+ // should had been h:m:s+1
+ // BUT, leave at least 2 decimals to round. Which shouldn't be a problem in
+ // practice because class Date can calculate only 8-digit days for it's
+ // sal_Int16 year range, which exactly leaves us with 14-4-8=2.
+ int nDec = 9;
+ const double fAbsTimeInDays = fabs( fTimeInDays);
+ if (fAbsTimeInDays >= 1.0)
+ {
+ const int nDig = static_cast<int>(ceil( log10( fAbsTimeInDays)));
+ nDec = std::clamp( 10 - nDig, 2, 9 );
+ }
+ double fSeconds = rtl::math::round( fRawSeconds, nDec);
+
+ // If this ended up as a full day the original value was very very close
+ // but not quite. Take that.
+ if (fSeconds >= tools::Time::secondPerDay)
+ fSeconds = fRawSeconds;
+
+ // Now do not round values (specifically not up), but truncate to the next
+ // magnitude, so 23:59:59.99 is still 23:59:59 and not 24:00:00 (or even
+ // 00:00:00 which Excel does).
+ nHour = fSeconds / tools::Time::secondPerHour;
+ fSeconds -= nHour * tools::Time::secondPerHour;
+ nMinute = fSeconds / tools::Time::secondPerMinute;
+ fSeconds -= nMinute * tools::Time::secondPerMinute;
+ nSecond = fSeconds;
+ fSeconds -= nSecond;
+
+ assert(fSeconds < 1.0); // or back to the drawing board...
+
+ if (nFractionDecimals > 0)
+ {
+ // Do not simply round the fraction, otherwise .999 would end up as .00
+ // again. Truncate instead if rounding would round up into an integer
+ // value.
+ fFractionOfSecond = rtl::math::round( fSeconds, nFractionDecimals);
+ if (fFractionOfSecond >= 1.0)
+ fFractionOfSecond = rtl::math::pow10Exp( std::trunc(
+ rtl::math::pow10Exp( fSeconds, nFractionDecimals)), -nFractionDecimals);
+ }
+ else
+ fFractionOfSecond = fSeconds;
+}
+
+Time& tools::Time::operator =( const tools::Time& rTime )
+{
+ nTime = rTime.nTime;
+ return *this;
+}
+
+Time& tools::Time::operator +=( const tools::Time& rTime )
+{
+ nTime = NanoSecToTime( TimeToNanoSec( *this ) +
+ TimeToNanoSec( rTime ) ).GetTime();
+ return *this;
+}
+
+Time& tools::Time::operator -=( const tools::Time& rTime )
+{
+ nTime = NanoSecToTime( TimeToNanoSec( *this ) -
+ TimeToNanoSec( rTime ) ).GetTime();
+ return *this;
+}
+
+Time operator +( const tools::Time& rTime1, const tools::Time& rTime2 )
+{
+ return NanoSecToTime( TimeToNanoSec( rTime1 ) +
+ TimeToNanoSec( rTime2 ) );
+}
+
+Time operator -( const tools::Time& rTime1, const tools::Time& rTime2 )
+{
+ return NanoSecToTime( TimeToNanoSec( rTime1 ) -
+ TimeToNanoSec( rTime2 ) );
+}
+
+bool tools::Time::IsEqualIgnoreNanoSec( const tools::Time& rTime ) const
+{
+ sal_Int32 n1 = (nTime < 0 ? -static_cast<sal_Int32>(GetNanoSec()) : GetNanoSec() );
+ sal_Int32 n2 = (rTime.nTime < 0 ? -static_cast<sal_Int32>(rTime.GetNanoSec()) : rTime.GetNanoSec() );
+ return (nTime - n1) == (rTime.nTime - n2);
+}
+
+Time tools::Time::GetUTCOffset()
+{
+#if defined(_WIN32)
+ TIME_ZONE_INFORMATION aTimeZone;
+ aTimeZone.Bias = 0;
+ DWORD nTimeZoneRet = GetTimeZoneInformation( &aTimeZone );
+ sal_Int32 nTempTime = aTimeZone.Bias;
+ if ( nTimeZoneRet == TIME_ZONE_ID_STANDARD )
+ nTempTime += aTimeZone.StandardBias;
+ else if ( nTimeZoneRet == TIME_ZONE_ID_DAYLIGHT )
+ nTempTime += aTimeZone.DaylightBias;
+ tools::Time aTime( 0, static_cast<sal_uInt16>(abs( nTempTime )) );
+ if ( nTempTime > 0 )
+ aTime = -aTime;
+ return aTime;
+#else
+ static sal_uInt64 nCacheTicks = 0;
+ static sal_Int32 nCacheSecOffset = -1;
+ sal_uInt64 nTicks = tools::Time::GetSystemTicks();
+ time_t nTime;
+ tm aTM;
+ short nTempTime;
+
+ // determine value again if needed
+ if ( (nCacheSecOffset == -1) ||
+ ((nTicks - nCacheTicks) > 360000) ||
+ ( nTicks < nCacheTicks ) // handle overflow
+ )
+ {
+ nTime = time( nullptr );
+ localtime_r( &nTime, &aTM );
+ auto nLocalTime = mktime( &aTM );
+#if defined(__sun)
+ // Solaris gmtime_r() seems not to handle daylight saving time
+ // flags correctly
+ auto nUTC = nLocalTime + ( aTM.tm_isdst == 0 ? timezone : altzone );
+#elif defined( LINUX )
+ // Linux mktime() seems not to handle tm_isdst correctly
+ auto nUTC = nLocalTime - aTM.tm_gmtoff;
+#else
+ gmtime_r( &nTime, &aTM );
+ auto nUTC = mktime( &aTM );
+#endif
+ nCacheTicks = nTicks;
+ nCacheSecOffset = (nLocalTime-nUTC) / 60;
+ }
+
+ nTempTime = abs( nCacheSecOffset );
+ tools::Time aTime( 0, static_cast<sal_uInt16>(nTempTime) );
+ if ( nCacheSecOffset < 0 )
+ aTime = -aTime;
+ return aTime;
+#endif
+}
+
+sal_uInt64 tools::Time::GetSystemTicks()
+{
+ return tools::Time::GetMonotonicTicks() / 1000;
+}
+
+#ifdef _WIN32
+static LARGE_INTEGER initPerformanceFrequency()
+{
+ LARGE_INTEGER nTicksPerSecond = { 0, 0 };
+ if (!QueryPerformanceFrequency(&nTicksPerSecond))
+ nTicksPerSecond.QuadPart = 0;
+ return nTicksPerSecond;
+}
+#endif
+
+sal_uInt64 tools::Time::GetMonotonicTicks()
+{
+#ifdef _WIN32
+ static const LARGE_INTEGER nTicksPerSecond = initPerformanceFrequency();
+ if (nTicksPerSecond.QuadPart > 0)
+ {
+ LARGE_INTEGER nPerformanceCount;
+ QueryPerformanceCounter(&nPerformanceCount);
+ return static_cast<sal_uInt64>(
+ ( nPerformanceCount.QuadPart * 1000 * 1000 ) / nTicksPerSecond.QuadPart );
+ }
+ else
+ {
+ return static_cast<sal_uInt64>( timeGetTime() * 1000 );
+ }
+#else
+ sal_uInt64 nMicroSeconds;
+#ifdef __MACH__
+ static mach_timebase_info_data_t info = { 0, 0 };
+ if ( 0 == info.numer )
+ mach_timebase_info( &info );
+ nMicroSeconds = mach_absolute_time() * static_cast<double>(info.numer / info.denom) / 1000;
+#else
+#if defined(_POSIX_TIMERS)
+ struct timespec currentTime;
+ clock_gettime( CLOCK_MONOTONIC, &currentTime );
+ nMicroSeconds
+ = static_cast<sal_uInt64>(currentTime.tv_sec) * 1000 * 1000 + currentTime.tv_nsec / 1000;
+#else
+ struct timeval currentTime;
+ gettimeofday( &currentTime, nullptr );
+ nMicroSeconds = static_cast<sal_uInt64>(currentTime.tv_sec) * 1000 * 1000 + currentTime.tv_usec;
+#endif
+#endif // __MACH__
+ return nMicroSeconds;
+#endif // _WIN32
+}
+
+} /* namespace tools */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/debug/debug.cxx b/tools/source/debug/debug.cxx
new file mode 100644
index 0000000000..197ba24504
--- /dev/null
+++ b/tools/source/debug/debug.cxx
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/debug.hxx>
+#include <sal/log.hxx>
+
+namespace {
+
+struct DebugData
+{
+ DbgTestSolarMutexProc pDbgTestSolarMutex;
+ bool bTestSolarMutexWasSet;
+
+ DebugData()
+ :pDbgTestSolarMutex( nullptr ), bTestSolarMutexWasSet(false)
+ {
+ }
+};
+
+}
+
+static DebugData aDebugData;
+
+void DbgSetTestSolarMutex( DbgTestSolarMutexProc pParam )
+{
+ aDebugData.pDbgTestSolarMutex = pParam;
+ if (pParam)
+ aDebugData.bTestSolarMutexWasSet = true;
+}
+
+void DbgTestSolarMutex()
+{
+ // don't warn if it was set at least once, because then we're probably just post-DeInitVCL()
+ SAL_WARN_IF(
+ !aDebugData.bTestSolarMutexWasSet && aDebugData.pDbgTestSolarMutex == nullptr, "tools.debug",
+ "no DbgTestSolarMutex function set");
+ if ( aDebugData.pDbgTestSolarMutex )
+ aDebugData.pDbgTestSolarMutex();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/fsys/fileutil.cxx b/tools/source/fsys/fileutil.cxx
new file mode 100644
index 0000000000..0e3512e5a1
--- /dev/null
+++ b/tools/source/fsys/fileutil.cxx
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <tools/fileutil.hxx>
+#if defined _WIN32
+#include <osl/file.hxx>
+#include <rtl/uri.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <davclnt.h>
+#endif
+
+namespace
+{
+#if defined _WIN32
+OUString UNCToDavURL(LPCWSTR sUNC)
+{
+ DWORD nSize = 1024;
+ auto bufURL(std::make_unique<wchar_t[]>(nSize));
+ DWORD nResult = DavGetHTTPFromUNCPath(sUNC, bufURL.get(), &nSize);
+ if (nResult == ERROR_INSUFFICIENT_BUFFER)
+ {
+ bufURL = std::make_unique<wchar_t[]>(nSize);
+ nResult = DavGetHTTPFromUNCPath(sUNC, bufURL.get(), &nSize);
+ }
+ // looks like on different Windowses this may or may not be URL encoded?
+ return nResult == ERROR_SUCCESS
+ ? ::rtl::Uri::encode(OUString(o3tl::toU(bufURL.get())), rtl_UriCharClassUric,
+ rtl_UriEncodeKeepEscapes, RTL_TEXTENCODING_UTF8)
+ : OUString();
+}
+#endif
+}
+
+namespace tools
+{
+bool IsMappedWebDAVPath([[maybe_unused]] const OUString& rURL, [[maybe_unused]] OUString* pRealURL)
+{
+#if defined _WIN32
+ if (rURL.startsWithIgnoreAsciiCase("file:"))
+ {
+ OUString aSystemPath;
+ if (osl::FileBase::getSystemPathFromFileURL(rURL, aSystemPath) == osl::FileBase::E_None)
+ {
+ DWORD nSize = MAX_PATH;
+ auto bufUNC(std::make_unique<char[]>(nSize));
+ DWORD nResult = WNetGetUniversalNameW(o3tl::toW(aSystemPath.getStr()),
+ UNIVERSAL_NAME_INFO_LEVEL, bufUNC.get(), &nSize);
+ if (nResult == ERROR_MORE_DATA)
+ {
+ bufUNC = std::make_unique<char[]>(nSize);
+ nResult = WNetGetUniversalNameW(o3tl::toW(aSystemPath.getStr()),
+ UNIVERSAL_NAME_INFO_LEVEL, bufUNC.get(), &nSize);
+ }
+ if (nResult == NO_ERROR || nResult == ERROR_BAD_DEVICE)
+ {
+ NETRESOURCEW aReq{};
+ if (nResult == ERROR_BAD_DEVICE) // The path could already be an UNC
+ aReq.lpRemoteName = const_cast<LPWSTR>(o3tl::toW(aSystemPath.getStr()));
+ else
+ {
+ auto pInfo = reinterpret_cast<LPUNIVERSAL_NAME_INFOW>(bufUNC.get());
+ aReq.lpRemoteName = pInfo->lpUniversalName;
+ }
+ nSize = 1024;
+ auto bufInfo(std::make_unique<char[]>(nSize));
+ LPWSTR pSystem = nullptr;
+ nResult = WNetGetResourceInformationW(&aReq, bufInfo.get(), &nSize, &pSystem);
+ if (nResult == ERROR_MORE_DATA)
+ {
+ bufInfo = std::make_unique<char[]>(nSize);
+ nResult = WNetGetResourceInformationW(&aReq, bufInfo.get(), &nSize, &pSystem);
+ }
+ if (nResult == NO_ERROR)
+ {
+ LPNETRESOURCEW pInfo = reinterpret_cast<LPNETRESOURCEW>(bufInfo.get());
+ if (wcscmp(pInfo->lpProvider, L"Web Client Network") == 0)
+ {
+ if (pRealURL)
+ *pRealURL = UNCToDavURL(aReq.lpRemoteName);
+ return true;
+ }
+ }
+ }
+ }
+ }
+#endif
+ return false;
+}
+
+} // namespace tools
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/fsys/urlobj.cxx b/tools/source/fsys/urlobj.cxx
new file mode 100644
index 0000000000..18ee57b18e
--- /dev/null
+++ b/tools/source/fsys/urlobj.cxx
@@ -0,0 +1,4892 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <tools/urlobj.hxx>
+#include <tools/debug.hxx>
+#include <tools/inetmime.hxx>
+#include <tools/stream.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/util/XStringWidth.hpp>
+#include <o3tl/enumarray.hxx>
+#include <osl/diagnose.h>
+#include <osl/file.hxx>
+#include <rtl/character.hxx>
+#include <rtl/string.h>
+#include <rtl/textenc.h>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <sal/types.h>
+
+#include <algorithm>
+#include <cassert>
+#include <limits>
+#include <memory>
+#include <string_view>
+
+#include <string.h>
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/base64.hxx>
+#include <comphelper/string.hxx>
+
+using namespace css;
+
+// INetURLObject
+
+/* The URI grammar (using RFC 2234 conventions).
+
+ Constructs of the form
+ {reference <rule1> using rule2}
+ stand for a rule matching the given rule1 specified in the given reference,
+ encoded to URI syntax using rule2 (as specified in this URI grammar).
+
+
+ ; RFC 1738, RFC 2396, RFC 2732, private
+ login = [user [":" password] "@"] hostport
+ user = *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ";" / "=" / "_" / "~")
+ password = *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ";" / "=" / "_" / "~")
+ hostport = host [":" port]
+ host = incomplete-hostname / hostname / IPv4address / IPv6reference
+ incomplete-hostname = *(domainlabel ".") domainlabel
+ hostname = *(domainlabel ".") toplabel ["."]
+ domainlabel = alphanum [*(alphanum / "-") alphanum]
+ toplabel = ALPHA [*(alphanum / "-") alphanum]
+ IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
+ IPv6reference = "[" hexpart [":" IPv4address] "]"
+ hexpart = (hexseq ["::" [hexseq]]) / ("::" [hexseq])
+ hexseq = hex4 *(":" hex4)
+ hex4 = 1*4HEXDIG
+ port = *DIGIT
+ escaped = "%" HEXDIG HEXDIG
+ reserved = "$" / "&" / "+" / "," / "/" / ":" / ";" / "=" / "?" / "@" / "[" / "]"
+ mark = "!" / "'" / "(" / ")" / "*" / "-" / "." / "_" / "~"
+ alphanum = ALPHA / DIGIT
+ unreserved = alphanum / mark
+ uric = escaped / reserved / unreserved
+ pchar = escaped / unreserved / "$" / "&" / "+" / "," / ":" / "=" / "@"
+
+
+ ; RFC 1738, RFC 2396
+ ftp-url = "FTP://" login ["/" segment *("/" segment) [";TYPE=" ("A" / "D" / "I")]]
+ segment = *pchar
+
+
+ ; RFC 1738, RFC 2396
+ http-url = "HTTP://" hostport ["/" segment *("/" segment) ["?" *uric]]
+ segment = *(pchar / ";")
+
+
+ ; RFC 1738, RFC 2396, <http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q188997&>
+ file-url = "FILE://" [host / "LOCALHOST" / netbios-name] ["/" segment *("/" segment)]
+ segment = *pchar
+ netbios-name = 1*{<alphanum / "!" / "#" / "$" / "%" / "&" / "'" / "(" / ")" / "-" / "." / "@" / "^" / "_" / "{" / "}" / "~"> using (escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "-" / "." / "@" / "_" / "~")}
+
+
+ ; RFC 2368, RFC 2396
+ mailto-url = "MAILTO:" [to] [headers]
+ to = {RFC 822 <#mailbox> using *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "@" / "_" / "~")}
+ headers = "?" header *("&" header)
+ header = hname "=" hvalue
+ hname = {RFC 822 <field-name> using *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "@" / "_" / "~")} / "BODY"
+ hvalue = {RFC 822 <field-body> using *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "@" / "_" / "~")}
+
+
+ ; private (see RFC 1738, RFC 2396)
+ vnd-sun-star-webdav-url = "VND.SUN.STAR.WEBDAV://" hostport ["/" segment *("/" segment) ["?" *uric]]
+ segment = *(pchar / ";")
+
+
+ ; private
+ private-url = "PRIVATE:" path ["?" *uric]
+ path = *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")
+
+
+ ; private
+ vnd-sun-star-help-url = "VND.SUN.STAR.HELP://" name *("/" segment) ["?" *uric]
+ name = *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ":" / ";" / "=" / "@" / "_" / "~")
+ segment = *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ":" / ";" / "=" / "@" / "_" / "~")
+
+
+ ; private
+ https-url = "HTTPS://" hostport ["/" segment *("/" segment) ["?" *uric]]
+ segment = *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ":" / ";" / "=" / "@" / "_" / "~")
+
+
+ ; private
+ slot-url = "SLOT:" path ["?" *uric]
+ path = *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")
+
+
+ ; private
+ macro-url = "MACRO:" path ["?" *uric]
+ path = *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")
+
+
+ ; private
+ javascript-url = "JAVASCRIPT:" *uric
+
+
+ ; RFC 2397
+ data-url = "DATA:" [mediatype] [";BASE64"] "," *uric
+ mediatype = [type "/" subtype] *(";" attribute "=" value)
+ type = {RFC 2045 <type> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / ":" / "?" / "@" / "_" / "~")}
+ subtype = {RFC 2045 <subtype> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / ":" / "?" / "@" / "_" / "~")}
+ attribute = {RFC 2045 <subtype> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / ":" / "?" / "@" / "_" / "~")}
+ value = {RFC 2045 <subtype> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / ":" / "?" / "@" / "_" / "~")}
+
+
+ ; RFC 2392, RFC 2396
+ cid-url = "CID:" {RFC 822 <addr-spec> using *uric}
+
+
+ ; private
+ vnd-sun-star-hier-url = "VND.SUN.STAR.HIER:" ["//"reg_name] *("/" *pchar)
+ reg_name = 1*(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ":" / ";" / "=" / "@" / "_" / "~")
+
+
+ ; private
+ uno-url = ".UNO:" path ["?" *uric]
+ path = *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")
+
+
+ ; private
+ component-url = ".COMPONENT:" path ["?" *uric]
+ path = *(escaped / alphanum / "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")
+
+
+ ; private
+ vnd-sun-star-pkg-url = "VND.SUN.STAR.PKG://" reg_name *("/" *pchar) ["?" *uric]
+ reg_name = 1*(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / ":" / ";" / "=" / "@" / "_" / "~")
+
+
+ ; RFC 2255
+ ldap-url = "LDAP://" [hostport] ["/" [dn ["?" [attrdesct *("," attrdesc)] ["?" ["base" / "one" / "sub"] ["?" [filter] ["?" extension *("," extension)]]]]]]
+ dn = {RFC 2253 <distinguishedName> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")}
+ attrdesc = {RFC 2251 <AttributeDescription> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")}
+ filter = {RFC 2254 <filter> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")}
+ extension = ["!"] ["X-"] extoken ["=" exvalue]
+ extoken = {RFC 2252 <oid> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / "/" / ":" / ";" / "@" / "_" / "~")}
+ exvalue = {RFC 2251 <LDAPString> using *(escaped / alphanum / "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "-" / "." / "/" / ":" / ";" / "=" / "@" / "_" / "~")}
+
+
+ ; private
+ db-url = "DB:" *uric
+
+
+ ; private
+ vnd-sun-star-cmd-url = "VND.SUN.STAR.CMD:" opaque_part
+ opaque_part = uric_no_slash *uric
+ uric_no_slash = unreserved / escaped / ";" / "?" / ":" / "@" / "&" / "=" / "+" / "$" / ","
+
+
+ ; RFC 1738
+ telnet-url = "TELNET://" login ["/"]
+
+
+ ; private
+ vnd-sun-star-expand-url = "VND.SUN.STAR.EXPAND:" opaque_part
+ opaque_part = uric_no_slash *uric
+ uric_no_slash = unreserved / escaped / ";" / "?" / ":" / "@" / "&" / "=" / "+" / "$" / ","
+
+
+ ; private
+ vnd-sun-star-tdoc-url = "VND.SUN.STAR.TDOC:/" segment *("/" segment)
+ segment = *pchar
+
+
+ ; private
+ unknown-url = scheme ":" 1*uric
+ scheme = ALPHA *(alphanum / "+" / "-" / ".")
+
+
+ ; private (http://ubiqx.org/cifs/Appendix-D.html):
+ smb-url = "SMB://" login ["/" segment *("/" segment) ["?" *uric]]
+ segment = *(pchar / ";")
+ */
+
+sal_Int32 INetURLObject::SubString::clear()
+{
+ sal_Int32 nDelta = -m_nLength;
+ m_nBegin = -1;
+ m_nLength = 0;
+ return nDelta;
+}
+
+sal_Int32 INetURLObject::SubString::set(OUStringBuffer & rString,
+ std::u16string_view rSubString)
+{
+ sal_Int32 nDelta = rSubString.size() - m_nLength;
+
+ rString.remove(m_nBegin, m_nLength);
+ rString.insert(m_nBegin, rSubString);
+
+ m_nLength = rSubString.size();
+ return nDelta;
+}
+
+sal_Int32 INetURLObject::SubString::set(OUString & rString,
+ std::u16string_view rSubString)
+{
+ sal_Int32 nDelta = rSubString.size() - m_nLength;
+
+ rString = OUString::Concat(rString.subView(0, m_nBegin)) +
+ rSubString + rString.subView(m_nBegin + m_nLength);
+
+ m_nLength = rSubString.size();
+ return nDelta;
+}
+
+sal_Int32 INetURLObject::SubString::set(OUStringBuffer & rString,
+ std::u16string_view rSubString,
+ sal_Int32 nTheBegin)
+{
+ m_nBegin = nTheBegin;
+ return set(rString, rSubString);
+}
+
+inline void INetURLObject::SubString::operator +=(sal_Int32 nDelta)
+{
+ if (isPresent())
+ m_nBegin = m_nBegin + nDelta;
+}
+
+int INetURLObject::SubString::compare(SubString const & rOther,
+ OUStringBuffer const & rThisString,
+ OUStringBuffer const & rOtherString) const
+{
+ sal_Int32 len = std::min(m_nLength, rOther.m_nLength);
+ sal_Unicode const * p1 = rThisString.getStr() + m_nBegin;
+ sal_Unicode const * end = p1 + len;
+ sal_Unicode const * p2 = rOtherString.getStr() + rOther.m_nBegin;
+ while (p1 != end) {
+ if (*p1 < *p2) {
+ return -1;
+ } else if (*p1 > *p2) {
+ return 1;
+ }
+ ++p1;
+ ++p2;
+ }
+ return m_nLength < rOther.m_nLength ? -1
+ : m_nLength > rOther.m_nLength ? 1
+ : 0;
+}
+
+struct INetURLObject::SchemeInfo
+{
+ OUString m_sScheme;
+ char const * m_pPrefix;
+ bool m_bAuthority;
+ bool m_bUser;
+ bool m_bAuth;
+ bool m_bPassword;
+ bool m_bHost;
+ bool m_bPort;
+ bool m_bHierarchical;
+ bool m_bQuery;
+};
+
+struct INetURLObject::PrefixInfo
+{
+ enum class Kind { Official, Internal, External }; // order is important!
+
+ char const * m_pPrefix;
+ char const * m_pTranslatedPrefix;
+ INetProtocol m_eScheme;
+ Kind m_eKind;
+};
+
+// static
+inline INetURLObject::SchemeInfo const &
+INetURLObject::getSchemeInfo(INetProtocol eTheScheme)
+{
+ static constexpr OUString EMPTY = u""_ustr;
+ static constexpr OUString FTP = u"ftp"_ustr;
+ static constexpr OUString HTTP = u"http"_ustr;
+ static constexpr OUString FILE1 = u"file"_ustr; // because FILE is already defined
+ static constexpr OUString MAILTO = u"mailto"_ustr;
+ static constexpr OUString VND_WEBDAV = u"vnd.sun.star.webdav"_ustr;
+ static constexpr OUString PRIVATE = u"private"_ustr;
+ static constexpr OUString VND_HELP = u"vnd.sun.star.help"_ustr;
+ static constexpr OUString HTTPS = u"https"_ustr;
+ static constexpr OUString SLOT = u"slot"_ustr;
+ static constexpr OUString MACRO = u"macro"_ustr;
+ static constexpr OUString JAVASCRIPT = u"javascript"_ustr;
+ static constexpr OUString DATA = u"data"_ustr;
+ static constexpr OUString CID = u"cid"_ustr;
+ static constexpr OUString VND_HIER = u"vnd.sun.star.hier"_ustr;
+ static constexpr OUString UNO = u".uno"_ustr;
+ static constexpr OUString COMPONENT = u".component"_ustr;
+ static constexpr OUString VND_PKG = u"vnd.sun.star.pkg"_ustr;
+ static constexpr OUString LDAP = u"ldap"_ustr;
+ static constexpr OUString DB = u"db"_ustr;
+ static constexpr OUString VND_CMD = u"vnd.sun.star.cmd"_ustr;
+ static constexpr OUString TELNET = u"telnet"_ustr;
+ static constexpr OUString VND_EXPAND = u"vnd.sun.star.expand"_ustr;
+ static constexpr OUString VND_TDOC = u"vnd.sun.star.tdoc"_ustr;
+ static constexpr OUString SMB = u"smb"_ustr;
+ static constexpr OUString HID = u"hid"_ustr;
+ static constexpr OUString SFTP = u"sftp"_ustr;
+ static constexpr OUString VND_CMIS = u"vnd.libreoffice.cmis"_ustr;
+
+ static o3tl::enumarray<INetProtocol, SchemeInfo> constexpr map = {
+ // [-loplugin:redundantfcast]:
+ SchemeInfo{
+ EMPTY, "", false, false, false, false, false, false, false, false},
+ SchemeInfo{
+ FTP, "ftp://", true, true, false, true, true, true, true,
+ false},
+ SchemeInfo{
+ HTTP, "http://", true, false, false, false, true, true, true,
+ true},
+ SchemeInfo{
+ FILE1, "file://", true, false, false, false, true, false, true,
+ false},
+ SchemeInfo{
+ MAILTO, "mailto:", false, false, false, false, false, false,
+ false, true},
+ SchemeInfo{
+ VND_WEBDAV, "vnd.sun.star.webdav://", true, false,
+ false, false, true, true, true, true},
+ SchemeInfo{
+ PRIVATE, "private:", false, false, false, false, false, false,
+ false, true},
+ SchemeInfo{
+ VND_HELP, "vnd.sun.star.help://", true, false, false,
+ false, false, false, true, true},
+ SchemeInfo{
+ HTTPS, "https://", true, false, false, false, true, true,
+ true, true},
+ SchemeInfo{
+ SLOT, "slot:", false, false, false, false, false, false, false,
+ true},
+ SchemeInfo{
+ MACRO, "macro:", false, false, false, false, false, false,
+ false, true},
+ SchemeInfo{
+ JAVASCRIPT, "javascript:", false, false, false, false, false,
+ false, false, false},
+ SchemeInfo{
+ DATA, "data:", false, false, false, false, false, false, false,
+ false},
+ SchemeInfo{
+ CID, "cid:", false, false, false, false, false, false, false,
+ false},
+ SchemeInfo{
+ VND_HIER, "vnd.sun.star.hier:", true, false, false,
+ false, false, false, true, false},
+ SchemeInfo{
+ UNO, ".uno:", false, false, false, false, false, false, false,
+ true},
+ SchemeInfo{
+ COMPONENT, ".component:", false, false, false, false, false,
+ false, false, true},
+ SchemeInfo{
+ VND_PKG, "vnd.sun.star.pkg://", true, false, false,
+ false, false, false, true, true},
+ SchemeInfo{
+ LDAP, "ldap://", true, false, false, false, true, true,
+ false, true},
+ SchemeInfo{
+ DB, "db:", false, false, false, false, false, false, false,
+ false},
+ SchemeInfo{
+ VND_CMD, "vnd.sun.star.cmd:", false, false, false,
+ false, false, false, false, false},
+ SchemeInfo{
+ TELNET, "telnet://", true, true, false, true, true, true,
+ true, false},
+ SchemeInfo{
+ VND_EXPAND, "vnd.sun.star.expand:", false, false,
+ false, false, false, false, false, false},
+ SchemeInfo{
+ VND_TDOC, "vnd.sun.star.tdoc:", false, false, false,
+ false, false, false, true, false},
+ SchemeInfo{
+ EMPTY, "", false, false, false, false, true, true, true, false },
+ SchemeInfo{
+ SMB, "smb://", true, true, false, true, true, true, true,
+ true},
+ SchemeInfo{
+ HID, "hid:", false, false, false, false, false, false, false,
+ true},
+ SchemeInfo{
+ SFTP, "sftp://", true, true, false, true, true, true, true,
+ true},
+ SchemeInfo{
+ VND_CMIS, "vnd.libreoffice.cmis://", true, true,
+ false, false, true, false, true, true} };
+ return map[eTheScheme];
+};
+
+inline INetURLObject::SchemeInfo const & INetURLObject::getSchemeInfo() const
+{
+ return getSchemeInfo(m_eScheme);
+}
+
+namespace {
+
+sal_Unicode getHexDigit(sal_uInt32 nWeight)
+{
+ assert(nWeight < 16);
+ static const sal_Unicode aDigits[16]
+ = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
+ 'D', 'E', 'F' };
+ return aDigits[nWeight];
+}
+
+}
+
+// static
+inline void INetURLObject::appendEscape(OUStringBuffer & rTheText,
+ sal_uInt32 nOctet)
+{
+ rTheText.append( '%' );
+ rTheText.append( getHexDigit(nOctet >> 4) );
+ rTheText.append( getHexDigit(nOctet & 15) );
+}
+
+namespace {
+
+enum
+{
+ PA = INetURLObject::PART_USER_PASSWORD,
+ PD = INetURLObject::PART_FPATH,
+ PE = INetURLObject::PART_AUTHORITY,
+ PF = INetURLObject::PART_REL_SEGMENT_EXTRA,
+ PG = INetURLObject::PART_URIC,
+ PH = INetURLObject::PART_HTTP_PATH,
+ PI = INetURLObject::PART_MESSAGE_ID_PATH,
+ PJ = INetURLObject::PART_MAILTO,
+ PK = INetURLObject::PART_PATH_BEFORE_QUERY,
+ PL = INetURLObject::PART_PCHAR,
+ PM = INetURLObject::PART_VISIBLE,
+ PN = INetURLObject::PART_VISIBLE_NONSPECIAL,
+ PO = INetURLObject::PART_UNO_PARAM_VALUE,
+ PP = INetURLObject::PART_UNAMBIGUOUS,
+ PQ = INetURLObject::PART_URIC_NO_SLASH,
+ PR = INetURLObject::PART_HTTP_QUERY,
+};
+
+sal_uInt32 const aMustEncodeMap[128]
+ = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* */ PP,
+/* ! */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* " */ PM+PN +PP,
+/* # */ PM,
+/* $ */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* % */ PM,
+/* & */ PA +PD+PE+PF+PG+PH+PI +PK+PL+PM+PN+PO +PQ+PR,
+/* ' */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* ( */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* ) */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* * */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* + */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO +PQ+PR,
+/* , */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN +PQ+PR,
+/* - */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* . */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* / */ +PD +PG+PH+PI+PJ+PK +PM+PN+PO,
+/* 0 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* 1 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* 2 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* 3 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* 4 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* 5 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* 6 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* 7 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* 8 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* 9 */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* : */ +PD+PE +PG+PH+PI+PJ+PK+PL+PM+PN+PO +PQ+PR,
+/* ; */ PA +PE+PF+PG+PH+PI+PJ+PK +PM +PQ+PR,
+/* < */ +PI +PM+PN +PP,
+/* = */ PA +PD+PE+PF+PG+PH +PK+PL+PM+PN +PQ+PR,
+/* > */ +PI +PM+PN +PP,
+/* ? */ +PG +PM +PO +PQ,
+/* @ */ +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* A */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* B */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* C */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* D */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* E */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* F */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* G */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* H */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* I */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* J */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* K */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* L */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* M */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* N */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* O */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* P */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* Q */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* R */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* S */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* T */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* U */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* V */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* W */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* X */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* Y */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* Z */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* [ */ PG +PM+PN+PO,
+/* \ */ +PM+PN +PP,
+/* ] */ PG +PM+PN+PO,
+/* ^ */ PM+PN +PP,
+/* _ */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* ` */ PM+PN +PP,
+/* a */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* b */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* c */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* d */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* e */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* f */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* g */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* h */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* i */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* j */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* k */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* l */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* m */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* n */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* o */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* p */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* q */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* r */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* s */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* t */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* u */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* v */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* w */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* x */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* y */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* z */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ+PR,
+/* { */ PM+PN +PP,
+/* | */ +PM+PN +PP,
+/* } */ PM+PN +PP,
+/* ~ */ PA +PD+PE+PF+PG+PH+PI+PJ+PK+PL+PM+PN+PO+PP+PQ,
+ 0 };
+
+bool mustEncode(sal_uInt32 nUTF32, INetURLObject::Part ePart)
+{
+ return !rtl::isAscii(nUTF32) || !(aMustEncodeMap[nUTF32] & ePart);
+}
+
+}
+
+void INetURLObject::setInvalid()
+{
+ m_aAbsURIRef.setLength(0);
+ m_eScheme = INetProtocol::NotValid;
+ m_aScheme.clear();
+ m_aUser.clear();
+ m_aAuth.clear();
+ m_aHost.clear();
+ m_aPort.clear();
+ m_aPath.clear();
+ m_aQuery.clear();
+ m_aFragment.clear();
+}
+
+namespace {
+
+std::unique_ptr<SvMemoryStream> memoryStream(
+ void const * data, sal_Int32 length)
+{
+ std::unique_ptr<char[]> b(
+ new char[length]);
+ memcpy(b.get(), data, length);
+ std::unique_ptr<SvMemoryStream> s(
+ new SvMemoryStream(b.get(), length, StreamMode::READ));
+ s->ObjectOwnsMemory(true);
+ // coverity[leaked_storage : FALSE] - belongs to SvMemoryStream s at this point
+ b.release();
+ return s;
+}
+
+}
+
+std::unique_ptr<SvMemoryStream> INetURLObject::getData() const
+{
+ if( GetProtocol() != INetProtocol::Data )
+ {
+ return nullptr;
+ }
+
+ OUString sURLPath = GetURLPath( DecodeMechanism::WithCharset, RTL_TEXTENCODING_ISO_8859_1 );
+ sal_Unicode const * pSkippedMediatype = INetMIME::scanContentType( sURLPath );
+ sal_Int32 nCharactersSkipped = pSkippedMediatype == nullptr
+ ? 0 : pSkippedMediatype-sURLPath.getStr();
+ if (sURLPath.match(",", nCharactersSkipped))
+ {
+ nCharactersSkipped += strlen(",");
+ OString sURLEncodedData(
+ sURLPath.getStr() + nCharactersSkipped,
+ sURLPath.getLength() - nCharactersSkipped,
+ RTL_TEXTENCODING_ISO_8859_1, OUSTRING_TO_OSTRING_CVTFLAGS);
+ return memoryStream(
+ sURLEncodedData.getStr(), sURLEncodedData.getLength());
+ }
+ else if (sURLPath.matchIgnoreAsciiCase(";base64,", nCharactersSkipped))
+ {
+ nCharactersSkipped += strlen(";base64,");
+ std::u16string_view sBase64Data = sURLPath.subView( nCharactersSkipped );
+ css::uno::Sequence< sal_Int8 > aDecodedData;
+ if (comphelper::Base64::decodeSomeChars(aDecodedData, sBase64Data)
+ == sBase64Data.size())
+ {
+ return memoryStream(
+ aDecodedData.getArray(), aDecodedData.getLength());
+ }
+ }
+ return nullptr;
+}
+
+namespace {
+
+FSysStyle guessFSysStyleByCounting(sal_Unicode const * pBegin,
+ sal_Unicode const * pEnd,
+ FSysStyle eStyle)
+{
+ DBG_ASSERT(eStyle
+ & (FSysStyle::Unix
+ | FSysStyle::Dos),
+ "guessFSysStyleByCounting(): Bad style");
+ DBG_ASSERT(std::numeric_limits< sal_Int32 >::min() < pBegin - pEnd
+ && pEnd - pBegin <= std::numeric_limits< sal_Int32 >::max(),
+ "guessFSysStyleByCounting(): Too big");
+ sal_Int32 nSlashCount
+ = (eStyle & FSysStyle::Unix) ?
+ 0 : std::numeric_limits< sal_Int32 >::min();
+ sal_Int32 nBackslashCount
+ = (eStyle & FSysStyle::Dos) ?
+ 0 : std::numeric_limits< sal_Int32 >::min();
+ while (pBegin != pEnd)
+ switch (*pBegin++)
+ {
+ case '/':
+ ++nSlashCount;
+ break;
+
+ case '\\':
+ ++nBackslashCount;
+ break;
+ }
+ return nSlashCount >= nBackslashCount ?
+ FSysStyle::Unix : FSysStyle::Dos;
+}
+
+OUString parseScheme(
+ sal_Unicode const ** begin, sal_Unicode const * end,
+ sal_uInt32 fragmentDelimiter)
+{
+ sal_Unicode const * p = *begin;
+ if (p != end && rtl::isAsciiAlpha(*p)) {
+ do {
+ ++p;
+ } while (p != end
+ && (rtl::isAsciiAlphanumeric(*p) || *p == '+' || *p == '-'
+ || *p == '.'));
+ // #i34835# To avoid problems with Windows file paths like "C:\foo",
+ // do not accept generic schemes that are only one character long:
+ if (end - p > 1 && p[0] == ':' && p[1] != fragmentDelimiter
+ && p - *begin >= 2)
+ {
+ OUString scheme(
+ OUString(*begin, p - *begin).toAsciiLowerCase());
+ *begin = p + 1;
+ return scheme;
+ }
+ }
+ return OUString();
+}
+
+}
+
+bool INetURLObject::setAbsURIRef(std::u16string_view rTheAbsURIRef,
+ EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset,
+ bool bSmart,
+ FSysStyle eStyle)
+{
+ sal_Unicode const * pPos = rTheAbsURIRef.data();
+ sal_Unicode const * pEnd = pPos + rTheAbsURIRef.size();
+
+ setInvalid();
+
+ sal_uInt32 nFragmentDelimiter = '#';
+
+ m_aAbsURIRef.setLength(0);
+
+ // Parse <scheme>:
+ sal_Unicode const * p = pPos;
+ PrefixInfo const * pPrefix = getPrefix(p, pEnd);
+ if (pPrefix)
+ {
+ pPos = p;
+ m_eScheme = pPrefix->m_eScheme;
+
+ char const * pTemp = pPrefix->m_eKind >= PrefixInfo::Kind::External ?
+ pPrefix->m_pTranslatedPrefix :
+ pPrefix->m_pPrefix;
+ m_aAbsURIRef.appendAscii(pTemp);
+ m_aScheme = SubString( 0, strstr(pTemp, ":") - pTemp );
+ }
+ else
+ {
+ if (bSmart)
+ {
+ // For scheme detection, the first (if any) of the following
+ // productions that matches the input string (and for which the
+ // appropriate style bit is set in eStyle, if applicable)
+ // determines the scheme. The productions use the auxiliary rules
+
+ // domain = label *("." label)
+ // label = alphanum [*(alphanum / "-") alphanum]
+ // alphanum = ALPHA / DIGIT
+ // IPv6reference = "[" IPv6address "]"
+ // IPv6address = hexpart [":" IPv4address]
+ // IPv4address = 1*3DIGIT 3("." 1*3DIGIT)
+ // hexpart = (hexseq ["::" [hexseq]]) / ("::" [hexseq])
+ // hexseq = hex4 *(":" hex4)
+ // hex4 = 1*4HEXDIG
+ // UCS4 = <any UCS4 character>
+
+ // 1st Production (known scheme; handled by the "if (pPrefix)" branch above):
+ // <one of the known schemes, ignoring case> ":" *UCS4
+ // 2nd Production (mailto):
+ // domain "@" domain
+ // 3rd Production (ftp):
+ // "FTP" 2*("." label) ["/" *UCS4]
+ // 4th Production (http):
+ // label 2*("." label) ["/" *UCS4]
+ // 5th Production (file):
+ // "//" (domain / IPv6reference) ["/" *UCS4]
+ // 6th Production (Unix file):
+ // "/" *UCS4
+ // 7th Production (UNC file; FSysStyle::Dos only):
+ // "\\" domain ["\" *UCS4]
+ // 8th Production (Unix-like DOS file; FSysStyle::Dos only):
+ // ALPHA ":" ["/" *UCS4]
+ // 9th Production (DOS file; FSysStyle::Dos only):
+ // ALPHA ":" ["\" *UCS4]
+ // 10th Production (any scheme; handled by the "m_eScheme = INetProtocol::Generic;" code
+ // after this else branch):
+ // <any scheme> ":" *UCS4
+
+ // For the 'non URL' file productions 6--9, the interpretation of
+ // the input as a (degenerate) URI is turned off, i.e., escape
+ // sequences and fragments are never detected as such, but are
+ // taken as literal characters.
+
+ sal_Unicode const * p1 = pPos;
+ if (eStyle & FSysStyle::Dos
+ && pEnd - p1 >= 2
+ && rtl::isAsciiAlpha(p1[0])
+ && p1[1] == ':'
+ && (pEnd - p1 == 2 || p1[2] == '/' || p1[2] == '\\'))
+ {
+ m_eScheme = INetProtocol::File; // 8th, 9th
+ eMechanism = EncodeMechanism::All;
+ nFragmentDelimiter = 0x80000000;
+ }
+ else if (eStyle & FSysStyle::Dos
+ && pEnd - p1 >= 6
+ && p1[0] == '\\' && p1[1] == '\\' && p1[2] == '?' && p1[3] == '\\'
+ && rtl::isAsciiAlpha(p1[4])
+ && p1[5] == ':'
+ && (pEnd - p1 == 6 || p1[6] == '/' || p1[6] == '\\'))
+ {
+ m_eScheme = INetProtocol::File; // 8th, 9th
+ eMechanism = EncodeMechanism::All;
+ nFragmentDelimiter = 0x80000000;
+ }
+ else if (pEnd - p1 >= 2 && p1[0] == '/' && p1[1] == '/')
+ {
+ p1 += 2;
+ if ((scanDomain(p1, pEnd) > 0 || scanIPv6reference(p1, pEnd))
+ && (p1 == pEnd || *p1 == '/'))
+ m_eScheme = INetProtocol::File; // 5th
+ }
+ else if (p1 != pEnd && *p1 == '/')
+ {
+ m_eScheme = INetProtocol::File; // 6th
+ eMechanism = EncodeMechanism::All;
+ nFragmentDelimiter = 0x80000000;
+ }
+ else if (eStyle & FSysStyle::Dos
+ && pEnd - p1 >= 2
+ && p1[0] == '\\'
+ && p1[1] == '\\')
+ {
+ p1 += 2;
+ if (pEnd - p1 >= 6 && p1[0] == '?' && p1[1] == '\\' && p1[5] == '\\'
+ && rtl::toAsciiLowerCase(p1[2]) == 'u'
+ && rtl::toAsciiLowerCase(p1[3]) == 'n'
+ && rtl::toAsciiLowerCase(p1[4]) == 'c')
+ {
+ p1 += 6; // "\\?\UNC\Servername\..."
+ }
+
+ sal_Int32 n = rtl_ustr_indexOfChar_WithLength(
+ p1, pEnd - p1, '\\');
+ sal_Unicode const * pe = n == -1 ? pEnd : p1 + n;
+ if (
+ parseHostOrNetBiosName(
+ p1, pe, EncodeMechanism::All, RTL_TEXTENCODING_DONTKNOW,
+ true, nullptr) ||
+ (scanDomain(p1, pe) > 0 && p1 == pe)
+ )
+ {
+ m_eScheme = INetProtocol::File; // 7th
+ eMechanism = EncodeMechanism::All;
+ nFragmentDelimiter = 0x80000000;
+ }
+ }
+ else
+ {
+ sal_Unicode const * pDomainEnd = p1;
+ sal_uInt32 nLabels = scanDomain(pDomainEnd, pEnd);
+ if (nLabels > 0 && pDomainEnd != pEnd && *pDomainEnd == '@')
+ {
+ ++pDomainEnd;
+ if (scanDomain(pDomainEnd, pEnd) > 0
+ && pDomainEnd == pEnd)
+ m_eScheme = INetProtocol::Mailto; // 2nd
+ }
+ else if (nLabels >= 3
+ && (pDomainEnd == pEnd || *pDomainEnd == '/'))
+ m_eScheme
+ = pDomainEnd - p1 >= 4
+ && (p1[0] == 'f' || p1[0] == 'F')
+ && (p1[1] == 't' || p1[1] == 'T')
+ && (p1[2] == 'p' || p1[2] == 'P')
+ && p1[3] == '.' ?
+ INetProtocol::Ftp : INetProtocol::Http; // 3rd, 4th
+ }
+ }
+
+ OUString aSynScheme;
+ if (m_eScheme == INetProtocol::NotValid) {
+ sal_Unicode const * p1 = pPos;
+ aSynScheme = parseScheme(&p1, pEnd, nFragmentDelimiter);
+ if (!aSynScheme.isEmpty())
+ {
+ if (bSmart && m_eSmartScheme != m_eScheme && p1 != pEnd && rtl::isAsciiDigit(*p1))
+ {
+ // rTheAbsURIRef doesn't define a known scheme (handled by the "if (pPrefix)"
+ // branch above); but a known scheme is defined in m_eSmartScheme. If this
+ // scheme may have a port in authority component, then avoid misinterpreting
+ // URLs like www.foo.bar:123/baz as using unknown "www.foo.bar" scheme with
+ // 123/baz rootless path. For now, do not try to handle possible colons in
+ // user information, require such ambiguous URLs to have explicit scheme part.
+ // Also ignore possibility of empty port.
+ const SchemeInfo& rInfo = getSchemeInfo(m_eSmartScheme);
+ if (rInfo.m_bAuthority && rInfo.m_bPort)
+ {
+ // Make sure that all characters from colon to [/?#] or to EOL are digits.
+ // Or maybe make it simple, and just assume that "xyz:1..." is more likely
+ // to be host "xyz" and port "1...", than scheme "xyz" and path "1..."?
+ sal_Unicode const* p2 = p1 + 1;
+ while (p2 != pEnd && rtl::isAsciiDigit(*p2))
+ ++p2;
+ if (p2 == pEnd || *p2 == '/' || *p2 == '?' || *p2 == '#')
+ m_eScheme = m_eSmartScheme;
+ }
+ }
+
+ if (m_eScheme == INetProtocol::NotValid)
+ {
+ m_eScheme = INetProtocol::Generic;
+ pPos = p1;
+ }
+ }
+ }
+
+ if (bSmart && m_eScheme == INetProtocol::NotValid && pPos != pEnd
+ && *pPos != nFragmentDelimiter)
+ {
+ m_eScheme = m_eSmartScheme;
+ }
+
+ if (m_eScheme == INetProtocol::NotValid)
+ {
+ setInvalid();
+ return false;
+ }
+
+ if (m_eScheme != INetProtocol::Generic) {
+ aSynScheme = getSchemeInfo().m_sScheme;
+ }
+ m_aScheme.set(m_aAbsURIRef, aSynScheme, m_aAbsURIRef.getLength());
+ m_aAbsURIRef.append(':');
+ }
+
+ sal_uInt32 nSegmentDelimiter = '/';
+ sal_uInt32 nAltSegmentDelimiter = 0x80000000;
+ bool bSkippedInitialSlash = false;
+
+ // Parse //<user>;AUTH=<auth>@<host>:<port> or
+ // //<user>:<password>@<host>:<port> or
+ // //<reg_name>
+ if (getSchemeInfo().m_bAuthority)
+ {
+ sal_Unicode const * pUserInfoBegin = nullptr;
+ sal_Unicode const * pUserInfoEnd = nullptr;
+ sal_Unicode const * pHostPortBegin = nullptr;
+ sal_Unicode const * pHostPortEnd = nullptr;
+
+ switch (m_eScheme)
+ {
+ case INetProtocol::VndSunStarHelp:
+ {
+ if (pEnd - pPos < 2 || *pPos++ != '/' || *pPos++ != '/')
+ {
+ setInvalid();
+ return false;
+ }
+ m_aAbsURIRef.append("//");
+ OUStringBuffer aSynAuthority;
+ while (pPos < pEnd
+ && *pPos != '/' && *pPos != '?'
+ && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd,
+ eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(aSynAuthority, nUTF32, eEscapeType,
+ PART_AUTHORITY, eCharset, false);
+ }
+ m_aHost.set(m_aAbsURIRef,
+ aSynAuthority,
+ m_aAbsURIRef.getLength());
+ // misusing m_aHost to store the authority
+ break;
+ }
+
+ case INetProtocol::VndSunStarHier:
+ {
+ if (pEnd - pPos >= 2 && pPos[0] == '/' && pPos[1] == '/')
+ {
+ pPos += 2;
+ m_aAbsURIRef.append("//");
+ OUStringBuffer aSynAuthority;
+ while (pPos < pEnd
+ && *pPos != '/' && *pPos != '?'
+ && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos,
+ pEnd,
+ eMechanism,
+ eCharset,
+ eEscapeType);
+ appendUCS4(aSynAuthority,
+ nUTF32,
+ eEscapeType,
+ PART_AUTHORITY,
+ eCharset,
+ false);
+ }
+ if (aSynAuthority.isEmpty())
+ {
+ setInvalid();
+ return false;
+ }
+ m_aHost.set(m_aAbsURIRef,
+ aSynAuthority,
+ m_aAbsURIRef.getLength());
+ // misusing m_aHost to store the authority
+ }
+ break;
+ }
+
+ case INetProtocol::VndSunStarPkg:
+ case INetProtocol::Cmis:
+ {
+ if (pEnd - pPos < 2 || *pPos++ != '/' || *pPos++ != '/')
+ {
+ setInvalid();
+ return false;
+ }
+ m_aAbsURIRef.append("//");
+ OUStringBuffer aSynUser(128);
+
+ bool bHasUser = false;
+ while (pPos < pEnd && *pPos != '@'
+ && *pPos != '/' && *pPos != '?'
+ && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd,
+ eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(aSynUser, nUTF32, eEscapeType,
+ PART_USER_PASSWORD, eCharset, false);
+
+ bHasUser = *pPos == '@';
+ }
+
+ OUStringBuffer aSynAuthority(64);
+ if ( !bHasUser )
+ {
+ aSynAuthority = aSynUser;
+ }
+ else
+ {
+ m_aUser.set(m_aAbsURIRef,
+ aSynUser,
+ m_aAbsURIRef.getLength());
+ m_aAbsURIRef.append("@");
+ ++pPos;
+
+ while (pPos < pEnd
+ && *pPos != '/' && *pPos != '?'
+ && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd,
+ eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(aSynAuthority, nUTF32, eEscapeType,
+ PART_AUTHORITY, eCharset, false);
+ }
+ }
+ if (aSynAuthority.isEmpty())
+ {
+ setInvalid();
+ return false;
+ }
+ m_aHost.set(m_aAbsURIRef,
+ aSynAuthority,
+ m_aAbsURIRef.getLength());
+ // misusing m_aHost to store the authority
+ break;
+ }
+
+ case INetProtocol::File:
+ if (bSmart)
+ {
+ // The first of the following seven productions that
+ // matches the rest of the input string (and for which the
+ // appropriate style bit is set in eStyle, if applicable)
+ // determines the used notation. The productions use the
+ // auxiliary rules
+
+ // domain = label *("." label)
+ // label = alphanum [*(alphanum / "-") alphanum]
+ // alphanum = ALPHA / DIGIT
+ // IPv6reference = "[" IPv6address "]"
+ // IPv6address = hexpart [":" IPv4address]
+ // IPv4address = 1*3DIGIT 3("." 1*3DIGIT)
+ // hexpart = (hexseq ["::" [hexseq]]) / ("::" [hexseq])
+ // hexseq = hex4 *(":" hex4)
+ // hex4 = 1*4HEXDIG
+ // path = <any UCS4 character except "#">
+ // UCS4 = <any UCS4 character>
+
+ // 1st Production (URL):
+ // "//" [domain / IPv6reference] ["/" *path]
+ // ["#" *UCS4]
+ // becomes
+ // "file://" domain "/" *path ["#" *UCS4]
+ if (pEnd - pPos >= 2 && pPos[0] == '/' && pPos[1] == '/')
+ {
+ sal_Unicode const * p1 = pPos + 2;
+ while (p1 != pEnd && *p1 != '/' &&
+ *p1 != nFragmentDelimiter)
+ {
+ ++p1;
+ }
+ if (parseHostOrNetBiosName(
+ pPos + 2, p1, EncodeMechanism::All,
+ RTL_TEXTENCODING_DONTKNOW, true, nullptr))
+ {
+ m_aAbsURIRef.append("//");
+ pHostPortBegin = pPos + 2;
+ pHostPortEnd = p1;
+ pPos = p1;
+ break;
+ }
+ }
+
+ // 2nd Production (MS IE generated 1; FSysStyle::Dos only):
+ // "//" ALPHA ":" ["/" *path] ["#" *UCS4]
+ // becomes
+ // "file:///" ALPHA ":" ["/" *path] ["#" *UCS4]
+ // replacing "\" by "/" within <*path>
+ // 3rd Production (MS IE generated 2; FSysStyle::Dos only):
+ // "//" ALPHA ":" ["\" *path] ["#" *UCS4]
+ // becomes
+ // "file:///" ALPHA ":" ["/" *path] ["#" *UCS4]
+ // replacing "\" by "/" within <*path>
+ // 4th Production (miscounted slashes):
+ // "//" *path ["#" *UCS4]
+ // becomes
+ // "file:///" *path ["#" *UCS4]
+ if (pEnd - pPos >= 2 && pPos[0] == '/' && pPos[1] == '/')
+ {
+ m_aAbsURIRef.append("//");
+ pPos += 2;
+ bSkippedInitialSlash = true;
+ if ((eStyle & FSysStyle::Dos)
+ && pEnd - pPos >= 2
+ && rtl::isAsciiAlpha(pPos[0])
+ && pPos[1] == ':'
+ && (pEnd - pPos == 2
+ || pPos[2] == '/' || pPos[2] == '\\'))
+ nAltSegmentDelimiter = '\\';
+ break;
+ }
+
+ // 5th Production (Unix):
+ // "/" *path ["#" *UCS4]
+ // becomes
+ // "file:///" *path ["#" *UCS4]
+ if (pPos < pEnd && *pPos == '/')
+ {
+ m_aAbsURIRef.append("//");
+ break;
+ }
+
+ // 6th Production (UNC; FSysStyle::Dos only):
+ // "\\" domain ["\" *path] ["#" *UCS4]
+ // becomes
+ // "file://" domain "/" *path ["#" *UCS4]
+ // replacing "\" by "/" within <*path>
+ if (eStyle & FSysStyle::Dos
+ && pEnd - pPos >= 2
+ && pPos[0] == '\\'
+ && pPos[1] == '\\')
+ {
+ sal_Unicode const * p1 = pPos + 2;
+ sal_Unicode const * pHostPortTentativeBegin = p1;
+ if (pEnd - p1 >= 6 && p1[0] == '?' && p1[1] == '\\' && p1[5] == '\\'
+ && rtl::toAsciiLowerCase(p1[2]) == 'u'
+ && rtl::toAsciiLowerCase(p1[3]) == 'n'
+ && rtl::toAsciiLowerCase(p1[4]) == 'c')
+ {
+ p1 += 6; // "\\?\UNC\Servername\..."
+ pHostPortTentativeBegin = p1;
+ }
+
+ sal_Unicode const * pe = p1;
+ while (pe < pEnd && *pe != '\\' &&
+ *pe != nFragmentDelimiter)
+ {
+ ++pe;
+ }
+ if (
+ parseHostOrNetBiosName(
+ p1, pe, EncodeMechanism::All,
+ RTL_TEXTENCODING_DONTKNOW, true, nullptr) ||
+ (scanDomain(p1, pe) > 0 && p1 == pe)
+ )
+ {
+ m_aAbsURIRef.append("//");
+ pHostPortBegin = pHostPortTentativeBegin;
+ pHostPortEnd = pe;
+ pPos = pe;
+ nSegmentDelimiter = '\\';
+ break;
+ }
+ }
+
+ // 7th Production (Unix-like DOS; FSysStyle::Dos only):
+ // ALPHA ":" ["/" *path] ["#" *UCS4]
+ // becomes
+ // "file:///" ALPHA ":" ["/" *path] ["#" *UCS4]
+ // replacing "\" by "/" within <*path>
+ // 8th Production (DOS; FSysStyle::Dos only):
+ // ALPHA ":" ["\" *path] ["#" *UCS4]
+ // becomes
+ // "file:///" ALPHA ":" ["/" *path] ["#" *UCS4]
+ // replacing "\" by "/" within <*path>
+ if (eStyle & FSysStyle::Dos)
+ {
+ sal_Unicode const* p1 = pPos;
+ if (pEnd - p1 >= 4 && p1[0] == '\\' && p1[1] == '\\' && p1[2] == '?'
+ && p1[3] == '\\')
+ p1 += 4; // "\\?\c:\..."
+
+ if (pEnd - p1 >= 2
+ && rtl::isAsciiAlpha(p1[0])
+ && p1[1] == ':'
+ && (pEnd - p1 == 2
+ || p1[2] == '/'
+ || p1[2] == '\\'))
+ {
+ pPos = p1;
+ m_aAbsURIRef.append("//");
+ nAltSegmentDelimiter = '\\';
+ bSkippedInitialSlash = true;
+ break;
+ }
+ }
+
+ // 9th Production (any):
+ // *path ["#" *UCS4]
+ // becomes
+ // "file:///" *path ["#" *UCS4]
+ // replacing the delimiter by "/" within <*path>. The
+ // delimiter is that character from the set { "/", "\"}
+ // which appears most often in <*path> (if FSysStyle::Unix
+ // is not among the style bits, "/" is removed from the
+ // set; if FSysStyle::Dos is not among the style bits, "\" is
+ // removed from the set). If two or
+ // more characters appear the same number of times, the
+ // character mentioned first in that set is chosen. If
+ // the first character of <*path> is the delimiter, that
+ // character is not copied
+ if (eStyle & (FSysStyle::Unix | FSysStyle::Dos))
+ {
+ m_aAbsURIRef.append("//");
+ switch (guessFSysStyleByCounting(pPos, pEnd, eStyle))
+ {
+ case FSysStyle::Unix:
+ nSegmentDelimiter = '/';
+ break;
+
+ case FSysStyle::Dos:
+ nSegmentDelimiter = '\\';
+ break;
+
+ default:
+ OSL_FAIL(
+ "INetURLObject::setAbsURIRef():"
+ " Bad guessFSysStyleByCounting");
+ break;
+ }
+ bSkippedInitialSlash
+ = pPos != pEnd && *pPos != nSegmentDelimiter;
+ break;
+ }
+ }
+ [[fallthrough]];
+ default:
+ {
+ // For INetProtocol::File, allow an empty authority ("//") to be
+ // missing if the following path starts with an explicit "/"
+ // (Java is notorious in generating such file URLs, so be
+ // liberal here):
+ if (pEnd - pPos >= 2 && pPos[0] == '/' && pPos[1] == '/')
+ pPos += 2;
+ else if (!bSmart
+ && !(m_eScheme == INetProtocol::File
+ && pPos != pEnd && *pPos == '/'))
+ {
+ setInvalid();
+ return false;
+ }
+ m_aAbsURIRef.append("//");
+
+ sal_Unicode const * pAuthority = pPos;
+ sal_uInt32 c = getSchemeInfo().m_bQuery ? '?' : 0x80000000;
+ while (pPos < pEnd && *pPos != '/' && *pPos != c
+ && *pPos != nFragmentDelimiter)
+ ++pPos;
+ if (getSchemeInfo().m_bUser)
+ if (getSchemeInfo().m_bHost)
+ {
+ sal_Unicode const * p1 = pAuthority;
+ while (p1 < pPos && *p1 != '@')
+ ++p1;
+ if (p1 == pPos)
+ {
+ pHostPortBegin = pAuthority;
+ pHostPortEnd = pPos;
+ }
+ else
+ {
+ pUserInfoBegin = pAuthority;
+ pUserInfoEnd = p1;
+ pHostPortBegin = p1 + 1;
+ pHostPortEnd = pPos;
+ }
+ }
+ else
+ {
+ pUserInfoBegin = pAuthority;
+ pUserInfoEnd = pPos;
+ }
+ else if (getSchemeInfo().m_bHost)
+ {
+ pHostPortBegin = pAuthority;
+ pHostPortEnd = pPos;
+ }
+ else if (pPos != pAuthority)
+ {
+ setInvalid();
+ return false;
+ }
+ break;
+ }
+ }
+
+ if (pUserInfoBegin)
+ {
+ Part ePart = PART_USER_PASSWORD;
+ bool bSupportsPassword = getSchemeInfo().m_bPassword;
+ bool bSupportsAuth
+ = !bSupportsPassword && getSchemeInfo().m_bAuth;
+ bool bHasAuth = false;
+ OUStringBuffer aSynUser;
+ sal_Unicode const * p1 = pUserInfoBegin;
+ while (p1 < pUserInfoEnd)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(p1, pUserInfoEnd,
+ eMechanism, eCharset, eEscapeType);
+ if (eEscapeType == EscapeType::NONE)
+ {
+ if (nUTF32 == ':' && bSupportsPassword)
+ {
+ bHasAuth = true;
+ break;
+ }
+ else if (nUTF32 == ';' && bSupportsAuth
+ && pUserInfoEnd - p1
+ > RTL_CONSTASCII_LENGTH("auth=")
+ && INetMIME::equalIgnoreCase(
+ p1,
+ p1 + RTL_CONSTASCII_LENGTH("auth="),
+ "auth="))
+ {
+ p1 += RTL_CONSTASCII_LENGTH("auth=");
+ bHasAuth = true;
+ break;
+ }
+ }
+ appendUCS4(aSynUser, nUTF32, eEscapeType, ePart,
+ eCharset, false);
+ }
+ m_aUser.set(m_aAbsURIRef, aSynUser, m_aAbsURIRef.getLength());
+ if (bHasAuth)
+ {
+ if (bSupportsPassword)
+ {
+ m_aAbsURIRef.append(':');
+ OUStringBuffer aSynAuth;
+ while (p1 < pUserInfoEnd)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(p1, pUserInfoEnd,
+ eMechanism, eCharset,
+ eEscapeType);
+ appendUCS4(aSynAuth, nUTF32, eEscapeType,
+ ePart, eCharset, false);
+ }
+ m_aAuth.set(m_aAbsURIRef, aSynAuth, m_aAbsURIRef.getLength());
+ }
+ else
+ {
+ m_aAbsURIRef.append(";AUTH=");
+ OUStringBuffer aSynAuth;
+ while (p1 < pUserInfoEnd)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(p1, pUserInfoEnd,
+ eMechanism, eCharset,
+ eEscapeType);
+ if (!INetMIME::isIMAPAtomChar(nUTF32))
+ {
+ setInvalid();
+ return false;
+ }
+ appendUCS4(aSynAuth, nUTF32, eEscapeType,
+ ePart, eCharset, false);
+ }
+ m_aAuth.set(m_aAbsURIRef, aSynAuth, m_aAbsURIRef.getLength());
+ }
+ }
+ if (pHostPortBegin)
+ m_aAbsURIRef.append('@');
+ }
+
+ if (pHostPortBegin)
+ {
+ sal_Unicode const * pPort = pHostPortEnd;
+ if ( getSchemeInfo().m_bPort && pHostPortBegin < pHostPortEnd )
+ {
+ sal_Unicode const * p1 = pHostPortEnd - 1;
+ while (p1 > pHostPortBegin && rtl::isAsciiDigit(*p1))
+ --p1;
+ if (*p1 == ':')
+ pPort = p1;
+ }
+ bool bNetBiosName = false;
+ switch (m_eScheme)
+ {
+ case INetProtocol::File:
+ // If the host equals "LOCALHOST" (unencoded and ignoring
+ // case), turn it into an empty host:
+ if (INetMIME::equalIgnoreCase(pHostPortBegin, pPort,
+ "localhost"))
+ pHostPortBegin = pPort;
+ bNetBiosName = true;
+ break;
+
+ case INetProtocol::Ldap:
+ case INetProtocol::Smb:
+ if (pHostPortBegin == pPort && pPort != pHostPortEnd)
+ {
+ setInvalid();
+ return false;
+ }
+ break;
+ default:
+ if (pHostPortBegin == pPort)
+ {
+ setInvalid();
+ return false;
+ }
+ break;
+ }
+ sal_Int32 nLenBeforeHost = m_aAbsURIRef.getLength();
+ if (!parseHostOrNetBiosName(
+ pHostPortBegin, pPort, eMechanism, eCharset,
+ bNetBiosName, &m_aAbsURIRef))
+ {
+ setInvalid();
+ return false;
+ }
+ m_aHost = SubString(nLenBeforeHost, m_aAbsURIRef.getLength() - nLenBeforeHost);
+ if (pPort != pHostPortEnd)
+ {
+ m_aAbsURIRef.append(':');
+ m_aPort.set(m_aAbsURIRef,
+ std::u16string_view{pPort + 1, static_cast<size_t>(pHostPortEnd - (pPort + 1))},
+ m_aAbsURIRef.getLength());
+ }
+ }
+ }
+
+ // Parse <path>
+ sal_Int32 nBeforePathLength = m_aAbsURIRef.getLength();
+ if (!parsePath(m_eScheme, &pPos, pEnd, eMechanism, eCharset,
+ bSkippedInitialSlash, nSegmentDelimiter,
+ nAltSegmentDelimiter,
+ getSchemeInfo().m_bQuery ? '?' : 0x80000000,
+ nFragmentDelimiter, m_aAbsURIRef))
+ {
+ setInvalid();
+ return false;
+ }
+ m_aPath = SubString(nBeforePathLength, m_aAbsURIRef.getLength() - nBeforePathLength);
+
+ // Parse ?<query>
+ if (getSchemeInfo().m_bQuery && pPos < pEnd && *pPos == '?')
+ {
+ m_aAbsURIRef.append('?');
+ OUStringBuffer aSynQuery;
+ for (++pPos; pPos < pEnd && *pPos != nFragmentDelimiter;)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd,
+ eMechanism, eCharset, eEscapeType);
+ appendUCS4(aSynQuery, nUTF32, eEscapeType,
+ PART_URIC, eCharset, true);
+ }
+ m_aQuery.set(m_aAbsURIRef, aSynQuery, m_aAbsURIRef.getLength());
+ }
+
+ // Parse #<fragment>
+ if (pPos < pEnd && *pPos == nFragmentDelimiter)
+ {
+ m_aAbsURIRef.append(sal_Unicode(nFragmentDelimiter));
+ OUStringBuffer aSynFragment;
+ for (++pPos; pPos < pEnd;)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd,
+ eMechanism, eCharset, eEscapeType);
+ appendUCS4(aSynFragment, nUTF32, eEscapeType, PART_URIC,
+ eCharset, true);
+ }
+ m_aFragment.set(m_aAbsURIRef, aSynFragment, m_aAbsURIRef.getLength());
+ }
+
+ if (pPos != pEnd)
+ {
+ setInvalid();
+ return false;
+ }
+
+ return true;
+}
+
+void INetURLObject::changeScheme(INetProtocol eTargetScheme) {
+ sal_Int32 oldSchemeLen = 0;
+ const OUString& rOldSchemeName = getSchemeInfo().m_sScheme;
+ if (m_eScheme == INetProtocol::Generic)
+ oldSchemeLen = m_aScheme.getLength();
+ else
+ oldSchemeLen = rOldSchemeName.getLength();
+ m_eScheme=eTargetScheme;
+ const OUString& rNewSchemeName = getSchemeInfo().m_sScheme;
+ sal_Int32 newSchemeLen = rNewSchemeName.getLength();
+ m_aAbsURIRef.remove(0, oldSchemeLen);
+ m_aAbsURIRef.insert(0, rNewSchemeName);
+ sal_Int32 delta=newSchemeLen-oldSchemeLen;
+ m_aUser+=delta;
+ m_aAuth+=delta;
+ m_aHost+=delta;
+ m_aPort+=delta;
+ m_aPath+=delta;
+ m_aQuery+=delta;
+ m_aFragment+=delta;
+}
+
+bool INetURLObject::convertRelToAbs(OUString const & rTheRelURIRef,
+ INetURLObject & rTheAbsURIRef,
+ bool & rWasAbsolute,
+ EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset,
+ bool bIgnoreFragment, bool bSmart,
+ bool bRelativeNonURIs, FSysStyle eStyle)
+ const
+{
+ sal_Unicode const * p = rTheRelURIRef.getStr();
+ sal_Unicode const * pEnd = p + rTheRelURIRef.getLength();
+
+ sal_Unicode const * pPrefixBegin = p;
+ PrefixInfo const * pPrefix = getPrefix(pPrefixBegin, pEnd);
+ bool hasScheme = pPrefix != nullptr;
+ if (!hasScheme) {
+ pPrefixBegin = p;
+ hasScheme = !parseScheme(&pPrefixBegin, pEnd, '#').isEmpty();
+ }
+
+ sal_uInt32 nSegmentDelimiter = '/';
+ sal_uInt32 nQueryDelimiter
+ = !bSmart || getSchemeInfo().m_bQuery ? '?' : 0x80000000;
+ sal_uInt32 nFragmentDelimiter = '#';
+ Part ePart = PART_VISIBLE;
+
+ if (!hasScheme && bSmart)
+ {
+ // If the input matches any of the following productions (for which
+ // the appropriate style bit is set in eStyle), it is assumed to be an
+ // absolute file system path, rather than a relative URI reference.
+ // (This is only a subset of the productions used for scheme detection
+ // in INetURLObject::setAbsURIRef(), because most of those productions
+ // interfere with the syntax of relative URI references.) The
+ // productions use the auxiliary rules
+
+ // domain = label *("." label)
+ // label = alphanum [*(alphanum / "-") alphanum]
+ // alphanum = ALPHA / DIGIT
+ // UCS4 = <any UCS4 character>
+
+ // 1st Production (UNC file; FSysStyle::Dos only):
+ // "\\" domain ["\" *UCS4]
+ // 2nd Production (Unix-like DOS file; FSysStyle::Dos only):
+ // ALPHA ":" ["/" *UCS4]
+ // 3rd Production (DOS file; FSysStyle::Dos only):
+ // ALPHA ":" ["\" *UCS4]
+ if (eStyle & FSysStyle::Dos)
+ {
+ bool bFSys = false;
+ sal_Unicode const * q = p;
+ if (pEnd - q >= 2
+ && rtl::isAsciiAlpha(q[0])
+ && q[1] == ':'
+ && (pEnd - q == 2 || q[2] == '/' || q[2] == '\\'))
+ bFSys = true; // 2nd, 3rd
+ else if (pEnd - q >= 2 && q[0] == '\\' && q[1] == '\\')
+ {
+ q += 2;
+ sal_Int32 n = rtl_ustr_indexOfChar_WithLength(
+ q, pEnd - q, '\\');
+ if (n == 1 && q[0] == '?')
+ {
+ // "\\?\c:\..." or "\\?\UNC\servername\..."
+ q += 2;
+ if (pEnd - q >= 2
+ && rtl::isAsciiAlpha(q[0])
+ && q[1] == ':'
+ && (pEnd - q == 2 || q[2] == '/' || q[2] == '\\'))
+ {
+ bFSys = true; // 2nd, 3rd
+ }
+ else if (pEnd - q >= 4
+ && q[3] == '\\'
+ && rtl::toAsciiLowerCase(q[0]) == 'u'
+ && rtl::toAsciiLowerCase(q[1]) == 'n'
+ && rtl::toAsciiLowerCase(q[2]) == 'c')
+ {
+ q += 4; // Check if it's 1st below
+ }
+ }
+ if (!bFSys)
+ {
+ sal_Unicode const * qe = n == -1 ? pEnd : q + n;
+ if (parseHostOrNetBiosName(
+ q, qe, EncodeMechanism::All, RTL_TEXTENCODING_DONTKNOW,
+ true, nullptr))
+ {
+ bFSys = true; // 1st
+ }
+ }
+ }
+ if (bFSys)
+ {
+ INetURLObject aNewURI;
+ aNewURI.setAbsURIRef(rTheRelURIRef, eMechanism,
+ eCharset, true, eStyle);
+ if (!aNewURI.HasError())
+ {
+ rTheAbsURIRef = aNewURI;
+ rWasAbsolute = true;
+ return true;
+ }
+ }
+ }
+
+ // When the base URL is a file URL, accept relative file system paths
+ // using "\" or ":" as delimiter (and ignoring URI conventions for "%"
+ // and "#"), as well as relative URIs using "/" as delimiter:
+ if (m_eScheme == INetProtocol::File)
+ switch (guessFSysStyleByCounting(p, pEnd, eStyle))
+ {
+ case FSysStyle::Unix:
+ nSegmentDelimiter = '/';
+ break;
+
+ case FSysStyle::Dos:
+ nSegmentDelimiter = '\\';
+ bRelativeNonURIs = true;
+ break;
+
+ default:
+ OSL_FAIL("INetURLObject::convertRelToAbs():"
+ " Bad guessFSysStyleByCounting");
+ break;
+ }
+
+ if (bRelativeNonURIs)
+ {
+ eMechanism = EncodeMechanism::All;
+ nQueryDelimiter = 0x80000000;
+ nFragmentDelimiter = 0x80000000;
+ ePart = PART_VISIBLE_NONSPECIAL;
+ }
+ }
+
+ // If the relative URI has the same scheme as the base URI, and that
+ // scheme is hierarchical, then ignore its presence in the relative
+ // URI in order to be backward compatible (cf. RFC 2396 section 5.2
+ // step 3):
+ if (pPrefix && pPrefix->m_eScheme == m_eScheme
+ && getSchemeInfo().m_bHierarchical)
+ {
+ hasScheme = false;
+ while (p != pEnd && *p++ != ':') ;
+ }
+ rWasAbsolute = hasScheme;
+
+ // Fast solution for non-relative URIs:
+ if (hasScheme)
+ {
+ INetURLObject aNewURI(rTheRelURIRef, eMechanism, eCharset);
+ if (aNewURI.HasError())
+ {
+ rWasAbsolute = false;
+ return false;
+ }
+
+ if (bIgnoreFragment)
+ aNewURI.clearFragment();
+ rTheAbsURIRef = aNewURI;
+ return true;
+ }
+
+ enum State { STATE_AUTH, STATE_ABS_PATH, STATE_REL_PATH, STATE_FRAGMENT,
+ STATE_DONE };
+
+ OUStringBuffer aSynAbsURIRef(128);
+ // make sure that the scheme is copied for generic schemes: getSchemeInfo().m_pScheme
+ // is empty ("") in that case, so take the scheme from m_aAbsURIRef
+ if (m_eScheme != INetProtocol::Generic)
+ {
+ aSynAbsURIRef.append(getSchemeInfo().m_sScheme);
+ }
+ else
+ {
+ sal_Unicode const * pSchemeBegin
+ = m_aAbsURIRef.getStr();
+ sal_Unicode const * pSchemeEnd = pSchemeBegin;
+ while (pSchemeEnd[0] != ':')
+ {
+ ++pSchemeEnd;
+ }
+ aSynAbsURIRef.append(pSchemeBegin, pSchemeEnd - pSchemeBegin);
+ }
+ aSynAbsURIRef.append(':');
+
+ State eState = STATE_AUTH;
+ bool bSameDoc = true;
+
+ if (getSchemeInfo().m_bAuthority)
+ {
+ if (pEnd - p >= 2 && p[0] == '/' && p[1] == '/')
+ {
+ aSynAbsURIRef.append("//");
+ p += 2;
+ eState = STATE_ABS_PATH;
+ bSameDoc = false;
+ while (p != pEnd)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32
+ = getUTF32(p, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ if (eEscapeType == EscapeType::NONE)
+ {
+ if (nUTF32 == nSegmentDelimiter)
+ break;
+ else if (nUTF32 == nFragmentDelimiter)
+ {
+ eState = STATE_FRAGMENT;
+ break;
+ }
+ }
+ appendUCS4(aSynAbsURIRef, nUTF32, eEscapeType,
+ PART_VISIBLE, eCharset, true);
+ }
+ }
+ else
+ {
+ SubString aAuthority(getAuthority());
+ aSynAbsURIRef.append(m_aAbsURIRef.getStr()
+ + aAuthority.getBegin(),
+ aAuthority.getLength());
+ }
+ }
+
+ if (eState == STATE_AUTH)
+ {
+ if (p == pEnd)
+ eState = STATE_DONE;
+ else if (*p == nFragmentDelimiter)
+ {
+ ++p;
+ eState = STATE_FRAGMENT;
+ }
+ else if (*p == nSegmentDelimiter)
+ {
+ ++p;
+ eState = STATE_ABS_PATH;
+ bSameDoc = false;
+ }
+ else
+ {
+ eState = STATE_REL_PATH;
+ bSameDoc = false;
+ }
+ }
+
+ if (eState == STATE_ABS_PATH)
+ {
+ aSynAbsURIRef.append('/');
+ eState = STATE_DONE;
+ while (p != pEnd)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32
+ = getUTF32(p, pEnd, eMechanism, eCharset, eEscapeType);
+ if (eEscapeType == EscapeType::NONE)
+ {
+ if (nUTF32 == nFragmentDelimiter)
+ {
+ eState = STATE_FRAGMENT;
+ break;
+ }
+ else if (nUTF32 == nSegmentDelimiter)
+ nUTF32 = '/';
+ }
+ appendUCS4(aSynAbsURIRef, nUTF32, eEscapeType, ePart,
+ eCharset, true);
+ }
+ }
+ else if (eState == STATE_REL_PATH)
+ {
+ if (!getSchemeInfo().m_bHierarchical)
+ {
+ // Detect cases where a relative input could not be made absolute
+ // because the given base URL is broken (most probably because it is
+ // empty):
+ SAL_WARN_IF(
+ HasError(), "tools.urlobj",
+ "cannot make <" << rTheRelURIRef
+ << "> absolute against broken base <"
+ << GetMainURL(DecodeMechanism::NONE) << ">");
+ rWasAbsolute = false;
+ return false;
+ }
+
+ sal_Unicode const * pBasePathBegin
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pBasePathEnd
+ = pBasePathBegin + m_aPath.getLength();
+ while (pBasePathEnd != pBasePathBegin)
+ if (*(--pBasePathEnd) == '/')
+ {
+ ++pBasePathEnd;
+ break;
+ }
+
+ sal_Int32 nPathBegin = aSynAbsURIRef.getLength();
+ aSynAbsURIRef.append(pBasePathBegin, pBasePathEnd - pBasePathBegin);
+ DBG_ASSERT(aSynAbsURIRef.getLength() > nPathBegin
+ && aSynAbsURIRef[aSynAbsURIRef.getLength() - 1] == '/',
+ "INetURLObject::convertRelToAbs(): Bad base path");
+
+ while (p != pEnd && *p != nQueryDelimiter && *p != nFragmentDelimiter)
+ {
+ if (*p == '.')
+ {
+ if (pEnd - p == 1
+ || p[1] == nSegmentDelimiter
+ || p[1] == nQueryDelimiter
+ || p[1] == nFragmentDelimiter)
+ {
+ ++p;
+ if (p != pEnd && *p == nSegmentDelimiter)
+ ++p;
+ continue;
+ }
+ else if (pEnd - p >= 2
+ && p[1] == '.'
+ && (pEnd - p == 2
+ || p[2] == nSegmentDelimiter
+ || p[2] == nQueryDelimiter
+ || p[2] == nFragmentDelimiter)
+ && aSynAbsURIRef.getLength() - nPathBegin > 1)
+ {
+ p += 2;
+ if (p != pEnd && *p == nSegmentDelimiter)
+ ++p;
+
+ sal_Int32 i = aSynAbsURIRef.getLength() - 2;
+ while (i > nPathBegin && aSynAbsURIRef[i] != '/')
+ --i;
+ aSynAbsURIRef.setLength(i + 1);
+ DBG_ASSERT(
+ aSynAbsURIRef.getLength() > nPathBegin
+ && aSynAbsURIRef[aSynAbsURIRef.getLength() - 1] == '/',
+ "INetURLObject::convertRelToAbs(): Bad base path");
+ continue;
+ }
+ }
+
+ while (p != pEnd
+ && *p != nSegmentDelimiter
+ && *p != nQueryDelimiter
+ && *p != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32
+ = getUTF32(p, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(aSynAbsURIRef, nUTF32, eEscapeType, ePart,
+ eCharset, true);
+ }
+ if (p != pEnd && *p == nSegmentDelimiter)
+ {
+ aSynAbsURIRef.append('/');
+ ++p;
+ }
+ }
+
+ while (p != pEnd && *p != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32
+ = getUTF32(p, pEnd, eMechanism, eCharset, eEscapeType);
+ appendUCS4(aSynAbsURIRef, nUTF32, eEscapeType, ePart,
+ eCharset, true);
+ }
+
+ if (p == pEnd)
+ eState = STATE_DONE;
+ else
+ {
+ ++p;
+ eState = STATE_FRAGMENT;
+ }
+ }
+ else if (bSameDoc)
+ {
+ aSynAbsURIRef.append(m_aAbsURIRef.getStr() + m_aPath.getBegin(),
+ m_aPath.getLength());
+ if (m_aQuery.isPresent())
+ aSynAbsURIRef.append(m_aAbsURIRef.getStr()
+ + m_aQuery.getBegin() - 1,
+ m_aQuery.getLength() + 1);
+ }
+
+ if (eState == STATE_FRAGMENT && !bIgnoreFragment)
+ {
+ aSynAbsURIRef.append('#');
+ while (p != pEnd)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32
+ = getUTF32(p, pEnd, eMechanism, eCharset, eEscapeType);
+ appendUCS4(aSynAbsURIRef, nUTF32, eEscapeType,
+ PART_VISIBLE, eCharset, true);
+ }
+ }
+
+ INetURLObject aNewURI(aSynAbsURIRef);
+ if (aNewURI.HasError())
+ {
+ // Detect cases where a relative input could not be made absolute
+ // because the given base URL is broken (most probably because it is
+ // empty):
+ SAL_WARN_IF(
+ HasError(), "tools.urlobj",
+ "cannot make <" << rTheRelURIRef
+ << "> absolute against broken base <" << GetMainURL(DecodeMechanism::NONE)
+ << ">");
+ rWasAbsolute = false;
+ return false;
+ }
+
+ rTheAbsURIRef = aNewURI;
+ return true;
+}
+
+bool INetURLObject::convertAbsToRel(OUString const & rTheAbsURIRef,
+ OUString & rTheRelURIRef,
+ EncodeMechanism eEncodeMechanism,
+ DecodeMechanism eDecodeMechanism,
+ rtl_TextEncoding eCharset,
+ FSysStyle eStyle) const
+{
+ // Check for hierarchical base URL:
+ if (!getSchemeInfo().m_bHierarchical)
+ {
+ rTheRelURIRef = decode(rTheAbsURIRef, eDecodeMechanism, eCharset);
+ return false;
+ }
+
+ // Convert the input (absolute or relative URI ref) to an absolute URI
+ // ref:
+ INetURLObject aSubject;
+ bool bWasAbsolute;
+ if (!convertRelToAbs(rTheAbsURIRef, aSubject, bWasAbsolute,
+ eEncodeMechanism, eCharset, false, false, false,
+ eStyle))
+ {
+ rTheRelURIRef = decode(rTheAbsURIRef, eDecodeMechanism, eCharset);
+ return false;
+ }
+
+ // Check for differing scheme or authority parts:
+ if ((m_aScheme.compare(
+ aSubject.m_aScheme, m_aAbsURIRef, aSubject.m_aAbsURIRef)
+ != 0)
+ || (m_aUser.compare(
+ aSubject.m_aUser, m_aAbsURIRef, aSubject.m_aAbsURIRef)
+ != 0)
+ || (m_aAuth.compare(
+ aSubject.m_aAuth, m_aAbsURIRef, aSubject.m_aAbsURIRef)
+ != 0)
+ || (m_aHost.compare(
+ aSubject.m_aHost, m_aAbsURIRef, aSubject.m_aAbsURIRef)
+ != 0)
+ || (m_aPort.compare(
+ aSubject.m_aPort, m_aAbsURIRef, aSubject.m_aAbsURIRef)
+ != 0))
+ {
+ rTheRelURIRef = aSubject.GetMainURL(eDecodeMechanism, eCharset);
+ return false;
+ }
+
+ sal_Unicode const * pBasePathBegin
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pBasePathEnd = pBasePathBegin + m_aPath.getLength();
+ sal_Unicode const * pSubjectPathBegin
+ = aSubject.m_aAbsURIRef.getStr() + aSubject.m_aPath.getBegin();
+ sal_Unicode const * pSubjectPathEnd
+ = pSubjectPathBegin + aSubject.m_aPath.getLength();
+
+ // Make nMatch point past the last matching slash, or past the end of the
+ // paths, in case they are equal:
+ sal_Unicode const * pSlash = nullptr;
+ sal_Unicode const * p1 = pBasePathBegin;
+ sal_Unicode const * p2 = pSubjectPathBegin;
+ for (;;)
+ {
+ if (p1 == pBasePathEnd || p2 == pSubjectPathEnd)
+ {
+ if (p1 == pBasePathEnd && p2 == pSubjectPathEnd)
+ pSlash = p1;
+ break;
+ }
+
+ sal_Unicode c = *p1++;
+ if (c != *p2++)
+ break;
+ if (c == '/')
+ pSlash = p1;
+ }
+ if (!pSlash)
+ {
+ // One of the paths does not start with '/':
+ rTheRelURIRef = aSubject.GetMainURL(eDecodeMechanism, eCharset);
+ return false;
+ }
+ sal_Int32 nMatch = pSlash - pBasePathBegin;
+
+ // If the two URLs are DOS file URLs starting with different volumes
+ // (e.g., file:///a:/... and file:///b:/...), the subject is not made
+ // relative (it could be, but some people do not like that):
+ if (m_eScheme == INetProtocol::File
+ && nMatch <= 1
+ && hasDosVolume(eStyle)
+ && aSubject.hasDosVolume(eStyle)) //TODO! ok to use eStyle for these?
+ {
+ rTheRelURIRef = aSubject.GetMainURL(eDecodeMechanism, eCharset);
+ return false;
+ }
+
+ // For every slash in the base path after nMatch, a prefix of "../" is
+ // added to the new relative URL (if the common prefix of the two paths is
+ // only "/"---but see handling of file URLs above---, the complete subject
+ // path could go into the new relative URL instead, but some people don't
+ // like that):
+ OUStringBuffer aSynRelURIRef;
+ for (sal_Unicode const * p = pBasePathBegin + nMatch; p != pBasePathEnd;
+ ++p)
+ {
+ if (*p == '/')
+ aSynRelURIRef.append("../");
+ }
+
+ // If the new relative URL would start with "//" (i.e., it would be
+ // mistaken for a relative URL starting with an authority part), or if the
+ // new relative URL would neither be empty nor start with <"/"> nor start
+ // with <1*rseg> (i.e., it could be mistaken for an absolute URL starting
+ // with a scheme part), then the new relative URL is prefixed with "./":
+ if (aSynRelURIRef.isEmpty())
+ {
+ if (pSubjectPathEnd - pSubjectPathBegin >= nMatch + 2
+ && pSubjectPathBegin[nMatch] == '/'
+ && pSubjectPathBegin[nMatch + 1] == '/')
+ {
+ aSynRelURIRef.append("./");
+ }
+ else
+ {
+ for (sal_Unicode const * p = pSubjectPathBegin + nMatch;
+ p != pSubjectPathEnd && *p != '/'; ++p)
+ {
+ if (mustEncode(*p, PART_REL_SEGMENT_EXTRA))
+ {
+ aSynRelURIRef.append("./");
+ break;
+ }
+ }
+ }
+ }
+
+ // The remainder of the subject path, starting at nMatch, is appended to
+ // the new relative URL:
+ aSynRelURIRef.append(decode(pSubjectPathBegin + nMatch, pSubjectPathEnd,
+ eDecodeMechanism, eCharset));
+
+ // If the subject has defined query or fragment parts, they are appended
+ // to the new relative URL:
+ if (aSubject.m_aQuery.isPresent())
+ {
+ aSynRelURIRef.append("?"
+ + aSubject.decode(aSubject.m_aQuery, eDecodeMechanism, eCharset));
+ }
+ if (aSubject.m_aFragment.isPresent())
+ {
+ aSynRelURIRef.append("#"
+ + aSubject.decode(aSubject.m_aFragment, eDecodeMechanism, eCharset));
+ }
+
+ rTheRelURIRef = aSynRelURIRef.makeStringAndClear();
+ return true;
+}
+
+// static
+bool INetURLObject::convertIntToExt(std::u16string_view rTheIntURIRef,
+ OUString & rTheExtURIRef,
+ DecodeMechanism eDecodeMechanism,
+ rtl_TextEncoding eCharset)
+{
+ OUStringBuffer aSynExtURIRef(256);
+ encodeText(aSynExtURIRef, rTheIntURIRef, PART_VISIBLE,
+ EncodeMechanism::NotCanonical, eCharset, true);
+ sal_Unicode const * pBegin = aSynExtURIRef.getStr();
+ sal_Unicode const * pEnd = pBegin + aSynExtURIRef.getLength();
+ sal_Unicode const * p = pBegin;
+ PrefixInfo const * pPrefix = getPrefix(p, pEnd);
+ bool bConvert = pPrefix && pPrefix->m_eKind == PrefixInfo::Kind::Internal;
+ if (bConvert)
+ {
+ comphelper::string::replaceAt(aSynExtURIRef, 0, p - pBegin,
+ OUString::createFromAscii(pPrefix->m_pTranslatedPrefix));
+ }
+ rTheExtURIRef = decode(aSynExtURIRef, eDecodeMechanism, eCharset);
+ return bConvert;
+}
+
+// static
+bool INetURLObject::convertExtToInt(std::u16string_view rTheExtURIRef,
+ OUString & rTheIntURIRef,
+ DecodeMechanism eDecodeMechanism,
+ rtl_TextEncoding eCharset)
+{
+ OUStringBuffer aSynIntURIRef(256);
+ encodeText(aSynIntURIRef, rTheExtURIRef, PART_VISIBLE,
+ EncodeMechanism::NotCanonical, eCharset, true);
+ sal_Unicode const * pBegin = aSynIntURIRef.getStr();
+ sal_Unicode const * pEnd = pBegin + aSynIntURIRef.getLength();
+ sal_Unicode const * p = pBegin;
+ PrefixInfo const * pPrefix = getPrefix(p, pEnd);
+ bool bConvert = pPrefix && pPrefix->m_eKind == PrefixInfo::Kind::External;
+ if (bConvert)
+ {
+ comphelper::string::replaceAt(aSynIntURIRef, 0, p - pBegin,
+ OUString::createFromAscii(pPrefix->m_pTranslatedPrefix));
+ }
+ rTheIntURIRef = decode(aSynIntURIRef, eDecodeMechanism, eCharset);
+ return bConvert;
+}
+
+// static
+INetURLObject::PrefixInfo const * INetURLObject::getPrefix(sal_Unicode const *& rBegin,
+ sal_Unicode const * pEnd)
+{
+ static PrefixInfo const aMap[]
+ = { // dummy entry at front needed, because pLast may point here:
+ { nullptr, nullptr, INetProtocol::NotValid, PrefixInfo::Kind::Internal },
+ { ".component:", "staroffice.component:", INetProtocol::Component,
+ PrefixInfo::Kind::Internal },
+ { ".uno:", "staroffice.uno:", INetProtocol::Uno,
+ PrefixInfo::Kind::Internal },
+ { "cid:", nullptr, INetProtocol::Cid, PrefixInfo::Kind::Official },
+ { "data:", nullptr, INetProtocol::Data, PrefixInfo::Kind::Official },
+ { "db:", "staroffice.db:", INetProtocol::Db, PrefixInfo::Kind::Internal },
+ { "file:", nullptr, INetProtocol::File, PrefixInfo::Kind::Official },
+ { "ftp:", nullptr, INetProtocol::Ftp, PrefixInfo::Kind::Official },
+ { "hid:", "staroffice.hid:", INetProtocol::Hid,
+ PrefixInfo::Kind::Internal },
+ { "http:", nullptr, INetProtocol::Http, PrefixInfo::Kind::Official },
+ { "https:", nullptr, INetProtocol::Https, PrefixInfo::Kind::Official },
+ { "javascript:", nullptr, INetProtocol::Javascript, PrefixInfo::Kind::Official },
+ { "ldap:", nullptr, INetProtocol::Ldap, PrefixInfo::Kind::Official },
+ { "macro:", "staroffice.macro:", INetProtocol::Macro,
+ PrefixInfo::Kind::Internal },
+ { "mailto:", nullptr, INetProtocol::Mailto, PrefixInfo::Kind::Official },
+ { "private:", "staroffice.private:", INetProtocol::PrivSoffice,
+ PrefixInfo::Kind::Internal },
+ { "private:factory/", "staroffice.factory:",
+ INetProtocol::PrivSoffice, PrefixInfo::Kind::Internal },
+ { "private:helpid/", "staroffice.helpid:", INetProtocol::PrivSoffice,
+ PrefixInfo::Kind::Internal },
+ { "private:java/", "staroffice.java:", INetProtocol::PrivSoffice,
+ PrefixInfo::Kind::Internal },
+ { "private:searchfolder:", "staroffice.searchfolder:",
+ INetProtocol::PrivSoffice, PrefixInfo::Kind::Internal },
+ { "private:trashcan:", "staroffice.trashcan:",
+ INetProtocol::PrivSoffice, PrefixInfo::Kind::Internal },
+ { "sftp:", nullptr, INetProtocol::Sftp, PrefixInfo::Kind::Official },
+ { "slot:", "staroffice.slot:", INetProtocol::Slot,
+ PrefixInfo::Kind::Internal },
+ { "smb:", nullptr, INetProtocol::Smb, PrefixInfo::Kind::Official },
+ { "staroffice.component:", ".component:", INetProtocol::Component,
+ PrefixInfo::Kind::External },
+ { "staroffice.db:", "db:", INetProtocol::Db, PrefixInfo::Kind::External },
+ { "staroffice.factory:", "private:factory/",
+ INetProtocol::PrivSoffice, PrefixInfo::Kind::External },
+ { "staroffice.helpid:", "private:helpid/", INetProtocol::PrivSoffice,
+ PrefixInfo::Kind::External },
+ { "staroffice.hid:", "hid:", INetProtocol::Hid,
+ PrefixInfo::Kind::External },
+ { "staroffice.java:", "private:java/", INetProtocol::PrivSoffice,
+ PrefixInfo::Kind::External },
+ { "staroffice.macro:", "macro:", INetProtocol::Macro,
+ PrefixInfo::Kind::External },
+ { "staroffice.private:", "private:", INetProtocol::PrivSoffice,
+ PrefixInfo::Kind::External },
+ { "staroffice.searchfolder:", "private:searchfolder:",
+ INetProtocol::PrivSoffice, PrefixInfo::Kind::External },
+ { "staroffice.slot:", "slot:", INetProtocol::Slot,
+ PrefixInfo::Kind::External },
+ { "staroffice.trashcan:", "private:trashcan:",
+ INetProtocol::PrivSoffice, PrefixInfo::Kind::External },
+ { "staroffice.uno:", ".uno:", INetProtocol::Uno,
+ PrefixInfo::Kind::External },
+ { "staroffice:", "private:", INetProtocol::PrivSoffice,
+ PrefixInfo::Kind::External },
+ { "telnet:", nullptr, INetProtocol::Telnet, PrefixInfo::Kind::Official },
+ { "vnd.libreoffice.cmis:", nullptr, INetProtocol::Cmis, PrefixInfo::Kind::Internal },
+ { "vnd.sun.star.cmd:", nullptr, INetProtocol::VndSunStarCmd,
+ PrefixInfo::Kind::Official },
+ { "vnd.sun.star.expand:", nullptr, INetProtocol::VndSunStarExpand,
+ PrefixInfo::Kind::Official },
+ { "vnd.sun.star.help:", nullptr, INetProtocol::VndSunStarHelp,
+ PrefixInfo::Kind::Official },
+ { "vnd.sun.star.hier:", nullptr, INetProtocol::VndSunStarHier,
+ PrefixInfo::Kind::Official },
+ { "vnd.sun.star.pkg:", nullptr, INetProtocol::VndSunStarPkg,
+ PrefixInfo::Kind::Official },
+ { "vnd.sun.star.tdoc:", nullptr, INetProtocol::VndSunStarTdoc,
+ PrefixInfo::Kind::Official },
+ { "vnd.sun.star.webdav:", nullptr, INetProtocol::VndSunStarWebdav,
+ PrefixInfo::Kind::Official }
+ };
+/* This list needs to be sorted, or you'll introduce serious bugs */
+
+ PrefixInfo const * pFirst = aMap + 1;
+ PrefixInfo const * pLast = aMap + sizeof aMap / sizeof (PrefixInfo) - 1;
+ PrefixInfo const * pMatch = nullptr;
+ sal_Unicode const * pMatched = rBegin;
+ sal_Unicode const * p = rBegin;
+ sal_Int32 i = 0;
+ for (; pFirst < pLast; ++i)
+ {
+ if (pFirst->m_pPrefix[i] == '\0')
+ {
+ pMatch = pFirst++;
+ pMatched = p;
+ }
+ if (p >= pEnd)
+ break;
+ sal_uInt32 nChar = rtl::toAsciiLowerCase(*p++);
+ while (pFirst <= pLast && static_cast<unsigned char>(pFirst->m_pPrefix[i]) < nChar)
+ ++pFirst;
+ while (pFirst <= pLast && static_cast<unsigned char>(pLast->m_pPrefix[i]) > nChar)
+ --pLast;
+ }
+ if (pFirst == pLast)
+ {
+ char const * q = pFirst->m_pPrefix + i;
+ while (p < pEnd && *q != '\0'
+ && rtl::toAsciiLowerCase(*p) == static_cast<unsigned char>(*q))
+ {
+ ++p;
+ ++q;
+ }
+ if (*q == '\0')
+ {
+ rBegin = p;
+ return pFirst;
+ }
+ }
+ rBegin = pMatched;
+ return pMatch;
+}
+
+sal_Int32 INetURLObject::getAuthorityBegin() const
+{
+ DBG_ASSERT(getSchemeInfo().m_bAuthority,
+ "INetURLObject::getAuthority(): Bad scheme");
+ sal_Int32 nBegin;
+ if (m_aUser.isPresent())
+ nBegin = m_aUser.getBegin();
+ else if (m_aHost.isPresent())
+ nBegin = m_aHost.getBegin();
+ else
+ nBegin = m_aPath.getBegin();
+ nBegin -= RTL_CONSTASCII_LENGTH("//");
+ DBG_ASSERT(m_aAbsURIRef[nBegin] == '/' && m_aAbsURIRef[nBegin + 1] == '/',
+ "INetURLObject::getAuthority(): Bad authority");
+ return nBegin;
+}
+
+INetURLObject::SubString INetURLObject::getAuthority() const
+{
+ sal_Int32 nBegin = getAuthorityBegin();
+ sal_Int32 nEnd = m_aPort.isPresent() ? m_aPort.getEnd() :
+ m_aHost.isPresent() ? m_aHost.getEnd() :
+ m_aAuth.isPresent() ? m_aAuth.getEnd() :
+ m_aUser.isPresent() ? m_aUser.getEnd() :
+ nBegin + RTL_CONSTASCII_LENGTH("//");
+ return SubString(nBegin, nEnd - nBegin);
+}
+
+bool INetURLObject::setUser(std::u16string_view rTheUser,
+ rtl_TextEncoding eCharset)
+{
+ if (
+ !getSchemeInfo().m_bUser
+ )
+ {
+ return false;
+ }
+
+ OUStringBuffer aNewUser;
+ encodeText(aNewUser, rTheUser, PART_USER_PASSWORD,
+ EncodeMechanism::WasEncoded, eCharset, false);
+ sal_Int32 nDelta;
+ if (m_aUser.isPresent())
+ nDelta = m_aUser.set(m_aAbsURIRef, aNewUser);
+ else if (m_aHost.isPresent())
+ {
+ m_aAbsURIRef.insert(m_aHost.getBegin(), u'@');
+ nDelta = m_aUser.set(m_aAbsURIRef, aNewUser, m_aHost.getBegin()) + 1;
+ }
+ else if (getSchemeInfo().m_bHost)
+ return false;
+ else
+ nDelta = m_aUser.set(m_aAbsURIRef, aNewUser, m_aPath.getBegin());
+ m_aAuth += nDelta;
+ m_aHost += nDelta;
+ m_aPort += nDelta;
+ m_aPath += nDelta;
+ m_aQuery += nDelta;
+ m_aFragment += nDelta;
+ return true;
+}
+
+namespace
+{
+ void lcl_Erase(OUStringBuffer &rBuf, sal_Int32 index, sal_Int32 count)
+ {
+ OUString sTemp(rBuf.makeStringAndClear());
+ rBuf.append(sTemp.replaceAt(index, count, u""));
+ }
+}
+
+bool INetURLObject::clearPassword()
+{
+ if (!getSchemeInfo().m_bPassword)
+ return false;
+ if (m_aAuth.isPresent())
+ {
+ lcl_Erase(m_aAbsURIRef, m_aAuth.getBegin() - 1,
+ m_aAuth.getLength() + 1);
+ sal_Int32 nDelta = m_aAuth.clear() - 1;
+ m_aHost += nDelta;
+ m_aPort += nDelta;
+ m_aPath += nDelta;
+ m_aQuery += nDelta;
+ m_aFragment += nDelta;
+ }
+ return true;
+}
+
+bool INetURLObject::setPassword(std::u16string_view rThePassword,
+ rtl_TextEncoding eCharset)
+{
+ if (!getSchemeInfo().m_bPassword)
+ return false;
+ OUStringBuffer aNewAuth;
+ encodeText(aNewAuth, rThePassword, PART_USER_PASSWORD,
+ EncodeMechanism::WasEncoded, eCharset, false);
+ sal_Int32 nDelta;
+ if (m_aAuth.isPresent())
+ nDelta = m_aAuth.set(m_aAbsURIRef, aNewAuth);
+ else if (m_aUser.isPresent())
+ {
+ m_aAbsURIRef.insert(m_aUser.getEnd(), u':');
+ nDelta
+ = m_aAuth.set(m_aAbsURIRef, aNewAuth, m_aUser.getEnd() + 1) + 1;
+ }
+ else if (m_aHost.isPresent())
+ {
+ m_aAbsURIRef.insert(m_aHost.getBegin(), ":@" );
+ m_aUser.set(m_aAbsURIRef, std::u16string_view{}, m_aHost.getBegin());
+ nDelta
+ = m_aAuth.set(m_aAbsURIRef, aNewAuth, m_aHost.getBegin() + 1) + 2;
+ }
+ else if (getSchemeInfo().m_bHost)
+ return false;
+ else
+ {
+ m_aAbsURIRef.insert(m_aPath.getBegin(), u':');
+ m_aUser.set(m_aAbsURIRef, std::u16string_view{}, m_aPath.getBegin());
+ nDelta
+ = m_aAuth.set(m_aAbsURIRef, aNewAuth, m_aPath.getBegin() + 1) + 1;
+ }
+ m_aHost += nDelta;
+ m_aPort += nDelta;
+ m_aPath += nDelta;
+ m_aQuery += nDelta;
+ m_aFragment += nDelta;
+ return true;
+}
+
+// static
+bool INetURLObject::parseHost(sal_Unicode const *& rBegin, sal_Unicode const * pEnd,
+ OUStringBuffer* pCanonic)
+{
+ // RFC 2373 is inconsistent about how to write an IPv6 address in which an
+ // IPv4 address directly follows the abbreviating "::". The ABNF in
+ // Appendix B suggests ":::13.1.68.3", while an example in 2.2/3 explicitly
+ // mentions "::13:1.68.3". This algorithm accepts both variants:
+ enum State { STATE_INITIAL, STATE_LABEL, STATE_LABEL_HYPHEN,
+ STATE_LABEL_DOT, STATE_TOPLABEL, STATE_TOPLABEL_HYPHEN,
+ STATE_TOPLABEL_DOT, STATE_IP4, STATE_IP4_DOT, STATE_IP6,
+ STATE_IP6_COLON, STATE_IP6_2COLON, STATE_IP6_3COLON,
+ STATE_IP6_HEXSEQ1, STATE_IP6_HEXSEQ1_COLON,
+ STATE_IP6_HEXSEQ1_MAYBE_IP4, STATE_IP6_HEXSEQ2,
+ STATE_IP6_HEXSEQ2_COLON, STATE_IP6_HEXSEQ2_MAYBE_IP4,
+ STATE_IP6_IP4, STATE_IP6_IP4_DOT, STATE_IP6_DONE };
+ sal_uInt32 nNumber = 0;
+ int nDigits = 0;
+ int nOctets = 0;
+ State eState = STATE_INITIAL;
+ sal_Unicode const * p = rBegin;
+ sal_Int32 nOriginalCanonicLength = pCanonic ? pCanonic->getLength() : 0;
+ for (; p != pEnd; ++p)
+ switch (eState)
+ {
+ case STATE_INITIAL:
+ if (*p == '[')
+ {
+ if (pCanonic)
+ pCanonic->append('[');
+ eState = STATE_IP6;
+ }
+ else if (rtl::isAsciiAlpha(*p) || *p == '_')
+ eState = STATE_TOPLABEL;
+ else if (rtl::isAsciiDigit(*p))
+ {
+ nNumber = INetMIME::getWeight(*p);
+ nDigits = 1;
+ nOctets = 1;
+ eState = STATE_IP4;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_LABEL:
+ if (*p == '.')
+ eState = STATE_LABEL_DOT;
+ else if (*p == '-')
+ eState = STATE_LABEL_HYPHEN;
+ else if (!rtl::isAsciiAlphanumeric(*p) && *p != '_')
+ goto done;
+ break;
+
+ case STATE_LABEL_HYPHEN:
+ if (rtl::isAsciiAlphanumeric(*p) || *p == '_')
+ eState = STATE_LABEL;
+ else if (*p != '-')
+ goto done;
+ break;
+
+ case STATE_LABEL_DOT:
+ if (rtl::isAsciiAlpha(*p) || *p == '_')
+ eState = STATE_TOPLABEL;
+ else if (rtl::isAsciiDigit(*p))
+ eState = STATE_LABEL;
+ else
+ goto done;
+ break;
+
+ case STATE_TOPLABEL:
+ if (*p == '.')
+ eState = STATE_TOPLABEL_DOT;
+ else if (*p == '-')
+ eState = STATE_TOPLABEL_HYPHEN;
+ else if (!rtl::isAsciiAlphanumeric(*p) && *p != '_')
+ goto done;
+ break;
+
+ case STATE_TOPLABEL_HYPHEN:
+ if (rtl::isAsciiAlphanumeric(*p) || *p == '_')
+ eState = STATE_TOPLABEL;
+ else if (*p != '-')
+ goto done;
+ break;
+
+ case STATE_TOPLABEL_DOT:
+ if (rtl::isAsciiAlpha(*p) || *p == '_')
+ eState = STATE_TOPLABEL;
+ else if (rtl::isAsciiDigit(*p))
+ eState = STATE_LABEL;
+ else
+ goto done;
+ break;
+
+ case STATE_IP4:
+ if (*p == '.')
+ if (nOctets < 4)
+ {
+ if (pCanonic)
+ {
+ pCanonic->append(static_cast<sal_Int64>(nNumber));
+ pCanonic->append( '.' );
+ }
+ ++nOctets;
+ eState = STATE_IP4_DOT;
+ }
+ else
+ eState = STATE_LABEL_DOT;
+ else if (*p == '-')
+ eState = STATE_LABEL_HYPHEN;
+ else if (rtl::isAsciiAlpha(*p) || *p == '_')
+ eState = STATE_LABEL;
+ else if (rtl::isAsciiDigit(*p))
+ if (nDigits < 3)
+ {
+ nNumber = 10 * nNumber + INetMIME::getWeight(*p);
+ ++nDigits;
+ }
+ else
+ eState = STATE_LABEL;
+ else
+ goto done;
+ break;
+
+ case STATE_IP4_DOT:
+ if (rtl::isAsciiAlpha(*p) || *p == '_')
+ eState = STATE_TOPLABEL;
+ else if (rtl::isAsciiDigit(*p))
+ {
+ nNumber = INetMIME::getWeight(*p);
+ nDigits = 1;
+ eState = STATE_IP4;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6:
+ if (*p == ':')
+ eState = STATE_IP6_COLON;
+ else if (rtl::isAsciiHexDigit(*p))
+ {
+ nNumber = INetMIME::getHexWeight(*p);
+ nDigits = 1;
+ eState = STATE_IP6_HEXSEQ1;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_COLON:
+ if (*p == ':')
+ {
+ if (pCanonic)
+ pCanonic->append("::");
+ eState = STATE_IP6_2COLON;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_2COLON:
+ if (*p == ']')
+ eState = STATE_IP6_DONE;
+ else if (*p == ':')
+ {
+ if (pCanonic)
+ pCanonic->append(':');
+ eState = STATE_IP6_3COLON;
+ }
+ else if (rtl::isAsciiDigit(*p))
+ {
+ nNumber = INetMIME::getWeight(*p);
+ nDigits = 1;
+ eState = STATE_IP6_HEXSEQ2_MAYBE_IP4;
+ }
+ else if (rtl::isAsciiHexDigit(*p))
+ {
+ nNumber = INetMIME::getHexWeight(*p);
+ nDigits = 1;
+ eState = STATE_IP6_HEXSEQ2;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_3COLON:
+ if (rtl::isAsciiDigit(*p))
+ {
+ nNumber = INetMIME::getWeight(*p);
+ nDigits = 1;
+ nOctets = 1;
+ eState = STATE_IP6_IP4;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_HEXSEQ1:
+ if (*p == ']')
+ {
+ if (pCanonic)
+ pCanonic->append(
+ OUString::number(nNumber, 16));
+ eState = STATE_IP6_DONE;
+ }
+ else if (*p == ':')
+ {
+ if (pCanonic)
+ {
+ pCanonic->append(
+ OUString::number(nNumber, 16));
+ pCanonic->append(':');
+ }
+ eState = STATE_IP6_HEXSEQ1_COLON;
+ }
+ else if (rtl::isAsciiHexDigit(*p) && nDigits < 4)
+ {
+ nNumber = 16 * nNumber + INetMIME::getHexWeight(*p);
+ ++nDigits;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_HEXSEQ1_COLON:
+ if (*p == ':')
+ {
+ if (pCanonic)
+ pCanonic->append(':');
+ eState = STATE_IP6_2COLON;
+ }
+ else if (rtl::isAsciiDigit(*p))
+ {
+ nNumber = INetMIME::getWeight(*p);
+ nDigits = 1;
+ eState = STATE_IP6_HEXSEQ1_MAYBE_IP4;
+ }
+ else if (rtl::isAsciiHexDigit(*p))
+ {
+ nNumber = INetMIME::getHexWeight(*p);
+ nDigits = 1;
+ eState = STATE_IP6_HEXSEQ1;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_HEXSEQ1_MAYBE_IP4:
+ if (*p == ']')
+ {
+ if (pCanonic)
+ pCanonic->append(
+ OUString::number(nNumber, 16));
+ eState = STATE_IP6_DONE;
+ }
+ else if (*p == ':')
+ {
+ if (pCanonic)
+ {
+ pCanonic->append(
+ OUString::number(nNumber, 16));
+ pCanonic->append(':');
+ }
+ eState = STATE_IP6_HEXSEQ1_COLON;
+ }
+ else if (*p == '.')
+ {
+ nNumber = 100 * (nNumber >> 8) + 10 * (nNumber >> 4 & 15)
+ + (nNumber & 15);
+ if (pCanonic)
+ {
+ pCanonic->append(
+ OUString::number(nNumber));
+ pCanonic->append('.');
+ }
+ nOctets = 2;
+ eState = STATE_IP6_IP4_DOT;
+ }
+ else if (rtl::isAsciiDigit(*p) && nDigits < 3)
+ {
+ nNumber = 16 * nNumber + INetMIME::getWeight(*p);
+ ++nDigits;
+ }
+ else if (rtl::isAsciiHexDigit(*p) && nDigits < 4)
+ {
+ nNumber = 16 * nNumber + INetMIME::getHexWeight(*p);
+ ++nDigits;
+ eState = STATE_IP6_HEXSEQ1;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_HEXSEQ2:
+ if (*p == ']')
+ {
+ if (pCanonic)
+ pCanonic->append(
+ OUString::number(nNumber, 16));
+ eState = STATE_IP6_DONE;
+ }
+ else if (*p == ':')
+ {
+ if (pCanonic)
+ {
+ pCanonic->append(
+ OUString::number(nNumber, 16));
+ pCanonic->append(':');
+ }
+ eState = STATE_IP6_HEXSEQ2_COLON;
+ }
+ else if (rtl::isAsciiHexDigit(*p) && nDigits < 4)
+ {
+ nNumber = 16 * nNumber + INetMIME::getHexWeight(*p);
+ ++nDigits;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_HEXSEQ2_COLON:
+ if (rtl::isAsciiDigit(*p))
+ {
+ nNumber = INetMIME::getWeight(*p);
+ nDigits = 1;
+ eState = STATE_IP6_HEXSEQ2_MAYBE_IP4;
+ }
+ else if (rtl::isAsciiHexDigit(*p))
+ {
+ nNumber = INetMIME::getHexWeight(*p);
+ nDigits = 1;
+ eState = STATE_IP6_HEXSEQ2;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_HEXSEQ2_MAYBE_IP4:
+ if (*p == ']')
+ {
+ if (pCanonic)
+ pCanonic->append(
+ OUString::number(nNumber, 16));
+ eState = STATE_IP6_DONE;
+ }
+ else if (*p == ':')
+ {
+ if (pCanonic)
+ {
+ pCanonic->append(
+ OUString::number(nNumber, 16));
+ pCanonic->append(':');
+ }
+ eState = STATE_IP6_HEXSEQ2_COLON;
+ }
+ else if (*p == '.')
+ {
+ nNumber = 100 * (nNumber >> 8) + 10 * (nNumber >> 4 & 15)
+ + (nNumber & 15);
+ if (pCanonic)
+ {
+ pCanonic->append(
+ OUString::number(nNumber));
+ pCanonic->append('.');
+ }
+ nOctets = 2;
+ eState = STATE_IP6_IP4_DOT;
+ }
+ else if (rtl::isAsciiDigit(*p) && nDigits < 3)
+ {
+ nNumber = 16 * nNumber + INetMIME::getWeight(*p);
+ ++nDigits;
+ }
+ else if (rtl::isAsciiHexDigit(*p) && nDigits < 4)
+ {
+ nNumber = 16 * nNumber + INetMIME::getHexWeight(*p);
+ ++nDigits;
+ eState = STATE_IP6_HEXSEQ2;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_IP4:
+ if (*p == ']')
+ if (nOctets == 4)
+ {
+ if (pCanonic)
+ pCanonic->append(
+ OUString::number(nNumber));
+ eState = STATE_IP6_DONE;
+ }
+ else
+ goto done;
+ else if (*p == '.')
+ if (nOctets < 4)
+ {
+ if (pCanonic)
+ {
+ pCanonic->append(
+ OUString::number(nNumber));
+ pCanonic->append('.');
+ }
+ ++nOctets;
+ eState = STATE_IP6_IP4_DOT;
+ }
+ else
+ goto done;
+ else if (rtl::isAsciiDigit(*p) && nDigits < 3)
+ {
+ nNumber = 10 * nNumber + INetMIME::getWeight(*p);
+ ++nDigits;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_IP4_DOT:
+ if (rtl::isAsciiDigit(*p))
+ {
+ nNumber = INetMIME::getWeight(*p);
+ nDigits = 1;
+ eState = STATE_IP6_IP4;
+ }
+ else
+ goto done;
+ break;
+
+ case STATE_IP6_DONE:
+ goto done;
+ }
+ done:
+ switch (eState)
+ {
+ case STATE_LABEL:
+ case STATE_TOPLABEL:
+ case STATE_TOPLABEL_DOT:
+ if (pCanonic)
+ {
+ pCanonic->setLength(nOriginalCanonicLength);
+ pCanonic->append(rBegin, p - rBegin);
+ }
+ rBegin = p;
+ return true;
+
+ case STATE_IP4:
+ if (nOctets == 4)
+ {
+ if (pCanonic)
+ pCanonic->append(
+ OUString::number(nNumber));
+ rBegin = p;
+ return true;
+ }
+ if (pCanonic)
+ pCanonic->setLength(nOriginalCanonicLength);
+ return false;
+
+ case STATE_IP6_DONE:
+ if (pCanonic)
+ pCanonic->append(']');
+ rBegin = p;
+ return true;
+
+ default:
+ if (pCanonic)
+ pCanonic->setLength(nOriginalCanonicLength);
+ return false;
+ }
+}
+
+// static
+bool INetURLObject::parseHostOrNetBiosName(
+ sal_Unicode const * pBegin, sal_Unicode const * pEnd,
+ EncodeMechanism eMechanism, rtl_TextEncoding eCharset, bool bNetBiosName,
+ OUStringBuffer* pCanonic)
+{
+ if (pBegin >= pEnd)
+ return true;
+ sal_Int32 nOriginalCanonicLength = pCanonic ? pCanonic->getLength() : 0;
+ if (sal_Unicode const* p = pBegin; parseHost(p, pEnd, pCanonic) && p == pEnd)
+ return true;
+ if (pCanonic)
+ pCanonic->setLength(nOriginalCanonicLength); // discard parseHost results
+ if (!bNetBiosName)
+ return false;
+ while (pBegin < pEnd)
+ {
+ EscapeType eEscapeType;
+ switch (sal_uInt32 nUTF32 = getUTF32(pBegin, pEnd, eMechanism, eCharset, eEscapeType))
+ {
+ default:
+ if (INetMIME::isVisible(nUTF32))
+ {
+ if (pCanonic)
+ appendUCS4(*pCanonic, nUTF32, eEscapeType, PART_URIC, eCharset, true);
+ break;
+ }
+ [[fallthrough]];
+ case '"':
+ case '*':
+ case '+':
+ case ',':
+ case '/':
+ case ':':
+ case ';':
+ case '<':
+ case '=':
+ case '>':
+ case '?':
+ case '[':
+ case '\\':
+ case ']':
+ case '`':
+ case '|':
+ if (pCanonic)
+ pCanonic->setLength(nOriginalCanonicLength);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool INetURLObject::setHost(std::u16string_view rTheHost,
+ rtl_TextEncoding eCharset)
+{
+ if (!getSchemeInfo().m_bHost)
+ return false;
+ OUStringBuffer aSynHost(rTheHost);
+ bool bNetBiosName = false;
+ switch (m_eScheme)
+ {
+ case INetProtocol::File:
+ {
+ if (OUString::unacquired(aSynHost).equalsIgnoreAsciiCase("localhost"))
+ {
+ aSynHost.setLength(0);
+ }
+ bNetBiosName = true;
+ }
+ break;
+ case INetProtocol::Ldap:
+ if (aSynHost.isEmpty() && m_aPort.isPresent())
+ return false;
+ break;
+
+ default:
+ if (aSynHost.isEmpty())
+ return false;
+ break;
+ }
+ if (!parseHostOrNetBiosName(
+ aSynHost.getStr(), aSynHost.getStr() + aSynHost.getLength(),
+ EncodeMechanism::WasEncoded, eCharset, bNetBiosName, &aSynHost))
+ return false;
+ sal_Int32 nDelta = m_aHost.set(m_aAbsURIRef, aSynHost);
+ m_aPort += nDelta;
+ m_aPath += nDelta;
+ m_aQuery += nDelta;
+ m_aFragment += nDelta;
+ return true;
+}
+
+// static
+bool INetURLObject::parsePath(INetProtocol eScheme,
+ sal_Unicode const ** pBegin,
+ sal_Unicode const * pEnd,
+ EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset,
+ bool bSkippedInitialSlash,
+ sal_uInt32 nSegmentDelimiter,
+ sal_uInt32 nAltSegmentDelimiter,
+ sal_uInt32 nQueryDelimiter,
+ sal_uInt32 nFragmentDelimiter,
+ OUStringBuffer &rSynPath)
+{
+ DBG_ASSERT(pBegin, "INetURLObject::parsePath(): Null output param");
+
+ sal_Unicode const * pPos = *pBegin;
+ const sal_Int32 nSynPathBeforeLen = rSynPath.getLength();
+ switch (eScheme)
+ {
+ case INetProtocol::NotValid:
+ return false;
+
+ case INetProtocol::Ftp:
+ if (pPos < pEnd && *pPos != '/' && *pPos != nFragmentDelimiter)
+ goto failed;
+ while (pPos < pEnd && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(rSynPath, nUTF32, eEscapeType,
+ PART_HTTP_PATH, eCharset, true);
+ }
+ if (rSynPath.getLength() - nSynPathBeforeLen == 0)
+ rSynPath.append('/');
+ break;
+
+ case INetProtocol::Http:
+ case INetProtocol::VndSunStarWebdav:
+ case INetProtocol::Https:
+ case INetProtocol::Smb:
+ case INetProtocol::Cmis:
+ if (pPos < pEnd && *pPos != '/' && *pPos != nQueryDelimiter
+ && *pPos != nFragmentDelimiter)
+ goto failed;
+ while (pPos < pEnd && *pPos != nQueryDelimiter
+ && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(rSynPath, nUTF32, eEscapeType,
+ PART_HTTP_PATH, eCharset, true);
+ }
+ if (rSynPath.getLength() - nSynPathBeforeLen == 0)
+ rSynPath.append('/');
+ break;
+
+ case INetProtocol::File:
+ {
+ if (bSkippedInitialSlash)
+ rSynPath.append('/');
+ else if (pPos < pEnd
+ && *pPos != nSegmentDelimiter
+ && *pPos != nAltSegmentDelimiter)
+ goto failed;
+ while (pPos < pEnd && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ if (eEscapeType == EscapeType::NONE)
+ {
+ if (nUTF32 == nSegmentDelimiter
+ || nUTF32 == nAltSegmentDelimiter)
+ {
+ rSynPath.append('/');
+ continue;
+ }
+ else if (nUTF32 == '|'
+ && (pPos == pEnd
+ || *pPos == nFragmentDelimiter
+ || *pPos == nSegmentDelimiter
+ || *pPos == nAltSegmentDelimiter)
+ && rSynPath.getLength() - nSynPathBeforeLen == 2
+ && rtl::isAsciiAlpha(rSynPath[nSynPathBeforeLen + 1]))
+ {
+ // A first segment of <ALPHA "|"> is translated to
+ // <ALPHA ":">:
+ rSynPath.append(':');
+ continue;
+ }
+ }
+ appendUCS4(rSynPath, nUTF32, eEscapeType,
+ PART_PCHAR, eCharset, true);
+ }
+ if (rSynPath.getLength() - nSynPathBeforeLen == 0)
+ rSynPath.append('/');
+ break;
+ }
+
+ case INetProtocol::Mailto:
+ while (pPos < pEnd && *pPos != nQueryDelimiter
+ && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(rSynPath, nUTF32, eEscapeType,
+ PART_MAILTO, eCharset, true);
+ }
+ break;
+
+
+ case INetProtocol::PrivSoffice:
+ case INetProtocol::Slot:
+ case INetProtocol::Hid:
+ case INetProtocol::Macro:
+ case INetProtocol::Uno:
+ case INetProtocol::Component:
+ case INetProtocol::Ldap:
+ while (pPos < pEnd && *pPos != nQueryDelimiter
+ && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(rSynPath, nUTF32, eEscapeType,
+ PART_PATH_BEFORE_QUERY, eCharset, true);
+ }
+ break;
+
+ case INetProtocol::VndSunStarHelp:
+ if (pPos == pEnd
+ || *pPos == nQueryDelimiter
+ || *pPos == nFragmentDelimiter)
+ rSynPath.append('/');
+ else
+ {
+ if (*pPos != '/')
+ goto failed;
+ while (pPos < pEnd && *pPos != nQueryDelimiter
+ && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd,
+ eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(rSynPath, nUTF32, eEscapeType,
+ PART_HTTP_PATH, eCharset, true);
+ }
+ }
+ break;
+
+ case INetProtocol::Javascript:
+ case INetProtocol::Data:
+ case INetProtocol::Cid:
+ case INetProtocol::Db:
+ while (pPos < pEnd && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(rSynPath, nUTF32, eEscapeType,
+ PART_URIC, eCharset, true);
+ }
+ break;
+
+ case INetProtocol::VndSunStarHier:
+ case INetProtocol::VndSunStarPkg:
+ if (pPos < pEnd && *pPos != '/'
+ && *pPos != nQueryDelimiter && *pPos != nFragmentDelimiter)
+ goto failed;
+ while (pPos < pEnd && *pPos != nQueryDelimiter
+ && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ if (eEscapeType == EscapeType::NONE && nUTF32 == '/')
+ rSynPath.append('/');
+ else
+ appendUCS4(rSynPath, nUTF32, eEscapeType,
+ PART_PCHAR, eCharset, false);
+ }
+ if (rSynPath.getLength() - nSynPathBeforeLen == 0)
+ rSynPath.append('/');
+ break;
+
+ case INetProtocol::VndSunStarCmd:
+ case INetProtocol::VndSunStarExpand:
+ {
+ if (pPos == pEnd || *pPos == nFragmentDelimiter)
+ goto failed;
+ Part ePart = PART_URIC_NO_SLASH;
+ while (pPos != pEnd && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(rSynPath, nUTF32, eEscapeType, ePart,
+ eCharset, true);
+ ePart = PART_URIC;
+ }
+ break;
+ }
+
+ case INetProtocol::Telnet:
+ if (pPos < pEnd)
+ {
+ if (*pPos != '/' || pEnd - pPos > 1)
+ goto failed;
+ ++pPos;
+ }
+ rSynPath.append('/');
+ break;
+
+ case INetProtocol::VndSunStarTdoc:
+ if (pPos == pEnd || *pPos != '/')
+ goto failed;
+ while (pPos < pEnd && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ if (eEscapeType == EscapeType::NONE && nUTF32 == '/')
+ rSynPath.append('/');
+ else
+ appendUCS4(rSynPath, nUTF32, eEscapeType,
+ PART_PCHAR, eCharset, false);
+ }
+ break;
+
+ case INetProtocol::Generic:
+ case INetProtocol::Sftp:
+ while (pPos < pEnd && *pPos != nFragmentDelimiter)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pPos, pEnd, eMechanism,
+ eCharset, eEscapeType);
+ appendUCS4(rSynPath, nUTF32, eEscapeType,
+ PART_URIC, eCharset, true);
+ }
+ if (rSynPath.isEmpty())
+ goto failed;
+ break;
+ default:
+ OSL_ASSERT(false);
+ break;
+ }
+
+ *pBegin = pPos;
+ return true;
+failed:
+ rSynPath.setLength(nSynPathBeforeLen);
+ return false;
+}
+
+bool INetURLObject::setPath(std::u16string_view rThePath,
+ EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset)
+{
+ OUStringBuffer aSynPath(256);
+ sal_Unicode const * p = rThePath.data();
+ sal_Unicode const * pEnd = p + rThePath.size();
+ if (!parsePath(m_eScheme, &p, pEnd, eMechanism, eCharset, false,
+ '/', 0x80000000, 0x80000000, 0x80000000, aSynPath)
+ || p != pEnd)
+ return false;
+ sal_Int32 nDelta = m_aPath.set(m_aAbsURIRef, aSynPath);
+ m_aQuery += nDelta;
+ m_aFragment += nDelta;
+ return true;
+}
+
+bool INetURLObject::checkHierarchical() const {
+ if (m_eScheme == INetProtocol::VndSunStarExpand) {
+ OSL_FAIL(
+ "INetURLObject::checkHierarchical vnd.sun.star.expand");
+ return true;
+ } else {
+ return getSchemeInfo().m_bHierarchical;
+ }
+}
+
+bool INetURLObject::Append(std::u16string_view rTheSegment,
+ EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset)
+{
+ return insertName(rTheSegment, false, LAST_SEGMENT, eMechanism, eCharset);
+}
+
+INetURLObject::SubString INetURLObject::getSegment(sal_Int32 nIndex,
+ bool bIgnoreFinalSlash)
+ const
+{
+ DBG_ASSERT(nIndex >= 0 || nIndex == LAST_SEGMENT,
+ "INetURLObject::getSegment(): Bad index");
+
+ if (!checkHierarchical())
+ return SubString();
+
+ sal_Unicode const * pPathBegin
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength();
+ sal_Unicode const * pSegBegin;
+ sal_Unicode const * pSegEnd;
+ if (nIndex == LAST_SEGMENT)
+ {
+ pSegEnd = pPathEnd;
+ if (bIgnoreFinalSlash && pSegEnd > pPathBegin && pSegEnd[-1] == '/')
+ --pSegEnd;
+ if (pSegEnd <= pPathBegin)
+ return SubString();
+ pSegBegin = pSegEnd - 1;
+ while (pSegBegin > pPathBegin && *pSegBegin != '/')
+ --pSegBegin;
+ }
+ else
+ {
+ pSegBegin = pPathBegin;
+ while (nIndex-- > 0)
+ do
+ {
+ ++pSegBegin;
+ if (pSegBegin >= pPathEnd)
+ return SubString();
+ }
+ while (*pSegBegin != '/');
+ pSegEnd = pSegBegin + 1;
+ while (pSegEnd < pPathEnd && *pSegEnd != '/')
+ ++pSegEnd;
+ }
+
+ return SubString(pSegBegin - m_aAbsURIRef.getStr(),
+ pSegEnd - pSegBegin);
+}
+
+bool INetURLObject::insertName(std::u16string_view rTheName,
+ bool bAppendFinalSlash, sal_Int32 nIndex,
+ EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset)
+{
+ DBG_ASSERT(nIndex >= 0 || nIndex == LAST_SEGMENT,
+ "INetURLObject::insertName(): Bad index");
+
+ if (!checkHierarchical())
+ return false;
+
+ sal_Unicode const * pPathBegin
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength();
+ sal_Unicode const * pPrefixEnd;
+ bool bInsertSlash;
+ sal_Unicode const * pSuffixBegin;
+ if (nIndex == LAST_SEGMENT)
+ {
+ pPrefixEnd = pPathEnd;
+ if (pPrefixEnd > pPathBegin &&
+ pPrefixEnd[-1] == '/')
+ {
+ --pPrefixEnd;
+ }
+ bInsertSlash = bAppendFinalSlash;
+ pSuffixBegin = pPathEnd;
+ }
+ else if (nIndex == 0)
+ {
+ pPrefixEnd = pPathBegin;
+ bInsertSlash =
+ (pPathBegin < pPathEnd && *pPathBegin != '/') ||
+ (pPathBegin == pPathEnd && bAppendFinalSlash);
+ pSuffixBegin =
+ (pPathEnd - pPathBegin == 1 && *pPathBegin == '/' &&
+ !bAppendFinalSlash)
+ ? pPathEnd : pPathBegin;
+ }
+ else
+ {
+ pPrefixEnd = pPathBegin;
+ sal_Unicode const * pEnd = pPathEnd;
+ if (pEnd > pPathBegin && pEnd[-1] == '/')
+ --pEnd;
+ bool bSkip = pPrefixEnd < pEnd && *pPrefixEnd == '/';
+ bInsertSlash = false;
+ pSuffixBegin = pPathEnd;
+ while (nIndex-- > 0)
+ for (;;)
+ {
+ if (bSkip)
+ ++pPrefixEnd;
+ bSkip = true;
+ if (pPrefixEnd >= pEnd)
+ {
+ if (nIndex == 0)
+ {
+ bInsertSlash = bAppendFinalSlash;
+ break;
+ }
+ else
+ return false;
+ }
+ if (*pPrefixEnd == '/')
+ {
+ pSuffixBegin = pPrefixEnd;
+ break;
+ }
+ }
+ }
+
+ OUStringBuffer aNewPath(256);
+ aNewPath.append(
+ OUString::Concat(std::u16string_view(pPathBegin, pPrefixEnd - pPathBegin))
+ + "/");
+ encodeText(aNewPath, rTheName, PART_PCHAR,
+ eMechanism, eCharset, true);
+ if (bInsertSlash) {
+ aNewPath.append('/');
+ }
+ aNewPath.append(pSuffixBegin, pPathEnd - pSuffixBegin);
+
+ return setPath(aNewPath, EncodeMechanism::NotCanonical,
+ RTL_TEXTENCODING_UTF8);
+}
+
+void INetURLObject::clearQuery()
+{
+ if (HasError())
+ return;
+ if (m_aQuery.isPresent())
+ {
+ lcl_Erase(m_aAbsURIRef, m_aQuery.getBegin() - 1,
+ m_aQuery.getLength() + 1);
+ m_aFragment += m_aQuery.clear() - 1;
+ }
+}
+
+bool INetURLObject::setQuery(std::u16string_view rTheQuery,
+ EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset)
+{
+ if (!getSchemeInfo().m_bQuery)
+ return false;
+ OUStringBuffer aNewQuery;
+ encodeText(aNewQuery, rTheQuery, PART_URIC,
+ eMechanism, eCharset, true);
+ sal_Int32 nDelta;
+ if (m_aQuery.isPresent())
+ nDelta = m_aQuery.set(m_aAbsURIRef, aNewQuery);
+ else
+ {
+ m_aAbsURIRef.insert(m_aPath.getEnd(), u'?');
+ nDelta = m_aQuery.set(m_aAbsURIRef, aNewQuery, m_aPath.getEnd() + 1)
+ + 1;
+ }
+ m_aFragment += nDelta;
+ return true;
+}
+
+bool INetURLObject::clearFragment()
+{
+ if (HasError())
+ return false;
+ if (m_aFragment.isPresent())
+ {
+ m_aAbsURIRef.setLength(m_aFragment.getBegin() - 1);
+ m_aFragment.clear();
+ }
+ return true;
+}
+
+bool INetURLObject::setFragment(std::u16string_view rTheFragment,
+ EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset)
+{
+ if (HasError())
+ return false;
+ OUStringBuffer aNewFragment;
+ encodeText(aNewFragment, rTheFragment, PART_URIC,
+ eMechanism, eCharset, true);
+ if (m_aFragment.isPresent())
+ m_aFragment.set(m_aAbsURIRef, aNewFragment);
+ else
+ {
+ m_aAbsURIRef.append('#');
+ m_aFragment.set(m_aAbsURIRef, aNewFragment, m_aAbsURIRef.getLength());
+ }
+ return true;
+}
+
+bool INetURLObject::hasDosVolume(FSysStyle eStyle) const
+{
+ sal_Unicode const * p = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ return (eStyle & FSysStyle::Dos)
+ && m_aPath.getLength() >= 3
+ && p[0] == '/'
+ && rtl::isAsciiAlpha(p[1])
+ && p[2] == ':'
+ && (m_aPath.getLength() == 3 || p[3] == '/');
+}
+
+// static
+void INetURLObject::encodeText( OUStringBuffer& rOutputBuffer,
+ sal_Unicode const * pBegin,
+ sal_Unicode const * pEnd,
+ Part ePart, EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset,
+ bool bKeepVisibleEscapes)
+{
+ while (pBegin < pEnd)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pBegin, pEnd,
+ eMechanism, eCharset, eEscapeType);
+ appendUCS4(rOutputBuffer, nUTF32, eEscapeType, ePart,
+ eCharset, bKeepVisibleEscapes);
+ }
+}
+
+// static
+OUString INetURLObject::decode(sal_Unicode const * pBegin,
+ sal_Unicode const * pEnd,
+ DecodeMechanism eMechanism,
+ rtl_TextEncoding eCharset)
+{
+ switch (eMechanism)
+ {
+ case DecodeMechanism::NONE:
+ return OUString(pBegin, pEnd - pBegin);
+
+ case DecodeMechanism::ToIUri:
+ eCharset = RTL_TEXTENCODING_UTF8;
+ break;
+
+ default:
+ break;
+ }
+ OUStringBuffer aResult(static_cast<int>(pEnd-pBegin));
+ while (pBegin < pEnd)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(pBegin, pEnd,
+ EncodeMechanism::WasEncoded, eCharset, eEscapeType);
+ switch (eEscapeType)
+ {
+ case EscapeType::NONE:
+ aResult.appendUtf32(nUTF32);
+ break;
+
+ case EscapeType::Octet:
+ appendEscape(aResult, nUTF32);
+ break;
+
+ case EscapeType::Utf32:
+ if (
+ rtl::isAscii(nUTF32) &&
+ (
+ eMechanism == DecodeMechanism::ToIUri ||
+ (
+ eMechanism == DecodeMechanism::Unambiguous &&
+ mustEncode(nUTF32, PART_UNAMBIGUOUS)
+ )
+ )
+ )
+ {
+ appendEscape(aResult, nUTF32);
+ }
+ else
+ aResult.appendUtf32(nUTF32);
+ break;
+ }
+ }
+ return aResult.makeStringAndClear();
+}
+
+OUString INetURLObject::GetURLNoPass(DecodeMechanism eMechanism,
+ rtl_TextEncoding eCharset) const
+{
+ INetURLObject aTemp(*this);
+ aTemp.clearPassword();
+ return aTemp.GetMainURL(eMechanism, eCharset);
+}
+
+OUString INetURLObject::GetURLNoMark(DecodeMechanism eMechanism,
+ rtl_TextEncoding eCharset) const
+{
+ INetURLObject aTemp(*this);
+ aTemp.clearFragment();
+ return aTemp.GetMainURL(eMechanism, eCharset);
+}
+
+OUString
+INetURLObject::getAbbreviated(
+ uno::Reference< util::XStringWidth > const & rStringWidth,
+ sal_Int32 nWidth,
+ DecodeMechanism eMechanism,
+ rtl_TextEncoding eCharset)
+ const
+{
+ OSL_ENSURE(rStringWidth.is(), "specification violation");
+ OUStringBuffer aBuffer;
+ // make sure that the scheme is copied for generic schemes: getSchemeInfo().m_pScheme
+ // is empty ("") in that case, so take the scheme from m_aAbsURIRef
+ if (m_eScheme != INetProtocol::Generic)
+ {
+ aBuffer.append(getSchemeInfo().m_sScheme);
+ }
+ else
+ {
+ if (!m_aAbsURIRef.isEmpty())
+ {
+ sal_Unicode const * pSchemeBegin
+ = m_aAbsURIRef.getStr();
+ sal_Unicode const * pSchemeEnd = pSchemeBegin;
+
+ while (pSchemeEnd[0] != ':')
+ {
+ ++pSchemeEnd;
+ }
+ aBuffer.append(pSchemeBegin, pSchemeEnd - pSchemeBegin);
+ }
+ }
+ aBuffer.append(':');
+ bool bAuthority = getSchemeInfo().m_bAuthority;
+ sal_Unicode const * pCoreBegin
+ = m_aAbsURIRef.getStr() + (bAuthority ? getAuthorityBegin() :
+ m_aPath.getBegin());
+ sal_Unicode const * pCoreEnd
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin() + m_aPath.getLength();
+ bool bSegment = false;
+ if (getSchemeInfo().m_bHierarchical)
+ {
+ OUString aRest;
+ if (m_aQuery.isPresent())
+ aRest = "?...";
+ else if (m_aFragment.isPresent())
+ aRest = "#...";
+ OUStringBuffer aTrailer;
+ sal_Unicode const * pBegin = pCoreBegin;
+ sal_Unicode const * pEnd = pCoreEnd;
+ sal_Unicode const * pPrefixBegin = pBegin;
+ sal_Unicode const * pSuffixEnd = pEnd;
+ bool bPrefix = true;
+ bool bSuffix = true;
+ do
+ {
+ if (bSuffix)
+ {
+ sal_Unicode const * p = pSuffixEnd - 1;
+ if (pSuffixEnd == pCoreEnd && *p == '/')
+ --p;
+ while (*p != '/')
+ --p;
+ if (bAuthority && p == pCoreBegin + 1)
+ --p;
+ OUString
+ aSegment(decode(p + (p == pBegin && pBegin != pCoreBegin ?
+ 1 : 0),
+ pSuffixEnd,
+ eMechanism,
+ eCharset));
+ pSuffixEnd = p;
+ OUStringBuffer aResult(aBuffer);
+ if (pSuffixEnd != pBegin)
+ aResult.append("...");
+ aResult.append(aSegment + aTrailer + aRest);
+ if (rStringWidth->
+ queryStringWidth(aResult.makeStringAndClear())
+ <= nWidth)
+ {
+ aTrailer.insert(0, aSegment);
+ bSegment = true;
+ pEnd = pSuffixEnd;
+ }
+ else
+ bSuffix = false;
+ if (pPrefixBegin > pSuffixEnd)
+ pPrefixBegin = pSuffixEnd;
+ if (pBegin == pEnd)
+ break;
+ }
+ if (bPrefix)
+ {
+ sal_Unicode const * p
+ = pPrefixBegin
+ + (bAuthority && pPrefixBegin == pCoreBegin ? 2 :
+ 1);
+ OSL_ASSERT(p <= pEnd);
+ while (p < pEnd && *p != '/')
+ ++p;
+ if (p == pCoreEnd - 1 && *p == '/')
+ ++p;
+ OUString
+ aSegment(decode(pPrefixBegin
+ + (pPrefixBegin == pCoreBegin ? 0 :
+ 1),
+ p == pEnd ? p : p + 1,
+ eMechanism,
+ eCharset));
+ pPrefixBegin = p;
+ OUStringBuffer aResult(aBuffer + aSegment);
+ if (pPrefixBegin != pEnd)
+ aResult.append("...");
+ aResult.append(aTrailer + aRest);
+ if (rStringWidth->
+ queryStringWidth(aResult.makeStringAndClear())
+ <= nWidth)
+ {
+ aBuffer.append(aSegment);
+ bSegment = true;
+ pBegin = pPrefixBegin;
+ }
+ else
+ bPrefix = false;
+ if (pPrefixBegin > pSuffixEnd)
+ pSuffixEnd = pPrefixBegin;
+ if (pBegin == pEnd)
+ break;
+ }
+ }
+ while (bPrefix || bSuffix);
+ if (bSegment)
+ {
+ if (pPrefixBegin != pBegin || pSuffixEnd != pEnd)
+ aBuffer.append("...");
+ aBuffer.append(aTrailer);
+ }
+ }
+ if (!bSegment)
+ aBuffer.append(decode(pCoreBegin,
+ pCoreEnd,
+ eMechanism,
+ eCharset));
+ if (m_aQuery.isPresent())
+ {
+ aBuffer.append("?" + decode(m_aQuery, eMechanism, eCharset));
+ }
+ if (m_aFragment.isPresent())
+ {
+ aBuffer.append("#" + decode(m_aFragment, eMechanism, eCharset));
+ }
+ if (!aBuffer.isEmpty())
+ {
+ OUStringBuffer aResult(aBuffer);
+ if (rStringWidth->queryStringWidth(aResult.makeStringAndClear())
+ > nWidth)
+ for (sal_Int32 i = aBuffer.getLength();;)
+ {
+ if (i == 0)
+ {
+ aBuffer.setLength(aBuffer.getLength() - 1);
+ if (aBuffer.isEmpty())
+ break;
+ }
+ else
+ {
+ aBuffer.setLength(--i);
+ aBuffer.append("...");
+ }
+ aResult = aBuffer;
+ if (rStringWidth->
+ queryStringWidth(aResult.makeStringAndClear())
+ <= nWidth)
+ break;
+ }
+ }
+ return aBuffer.makeStringAndClear();
+}
+
+bool INetURLObject::operator ==(INetURLObject const & rObject) const
+{
+ if (m_eScheme != rObject.m_eScheme)
+ return false;
+ if (m_eScheme == INetProtocol::NotValid)
+ return std::u16string_view(m_aAbsURIRef) == std::u16string_view(rObject.m_aAbsURIRef);
+ if ((m_aScheme.compare(
+ rObject.m_aScheme, m_aAbsURIRef, rObject.m_aAbsURIRef)
+ != 0)
+ || GetUser(DecodeMechanism::NONE) != rObject.GetUser(DecodeMechanism::NONE)
+ || GetPass(DecodeMechanism::NONE) != rObject.GetPass(DecodeMechanism::NONE)
+ || !GetHost(DecodeMechanism::NONE).equalsIgnoreAsciiCase(
+ rObject.GetHost(DecodeMechanism::NONE))
+ || GetPort() != rObject.GetPort()
+ || HasParam() != rObject.HasParam()
+ || GetParam() != rObject.GetParam())
+ return false;
+ OUString aPath1(GetURLPath(DecodeMechanism::NONE));
+ OUString aPath2(rObject.GetURLPath(DecodeMechanism::NONE));
+ switch (m_eScheme)
+ {
+ case INetProtocol::File:
+ {
+ // If the URL paths of two file URLs only differ in that one has a
+ // final '/' and the other has not, take the two paths as
+ // equivalent (this could be useful for other schemes, too):
+ sal_Int32 nLength = aPath1.getLength();
+ switch (nLength - aPath2.getLength())
+ {
+ case -1:
+ if (aPath2[nLength] != '/')
+ return false;
+ break;
+
+ case 0:
+ break;
+
+ case 1:
+ if (aPath1[--nLength] != '/')
+ return false;
+ break;
+
+ default:
+ return false;
+ }
+ return aPath1.compareTo(aPath2, nLength) == 0;
+ }
+
+ default:
+ return aPath1 == aPath2;
+ }
+}
+
+bool INetURLObject::ConcatData(INetProtocol eTheScheme,
+ std::u16string_view rTheUser,
+ std::u16string_view rThePassword,
+ std::u16string_view rTheHost,
+ sal_uInt32 nThePort,
+ std::u16string_view rThePath)
+{
+ setInvalid();
+ m_eScheme = eTheScheme;
+ if (HasError() || m_eScheme == INetProtocol::Generic)
+ return false;
+ m_aAbsURIRef.setLength(0);
+ m_aAbsURIRef.append(getSchemeInfo().m_sScheme);
+ m_aAbsURIRef.append(':');
+ if (getSchemeInfo().m_bAuthority)
+ {
+ m_aAbsURIRef.append("//");
+ bool bUserInfo = false;
+ if (getSchemeInfo().m_bUser)
+ {
+ if (!rTheUser.empty())
+ {
+ OUStringBuffer aNewUser;
+ encodeText(aNewUser, rTheUser, PART_USER_PASSWORD,
+ EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, false);
+ m_aUser.set(m_aAbsURIRef, aNewUser, m_aAbsURIRef.getLength());
+ bUserInfo = true;
+ }
+ }
+ else if (!rTheUser.empty())
+ {
+ setInvalid();
+ return false;
+ }
+ if (!rThePassword.empty())
+ {
+ if (getSchemeInfo().m_bPassword)
+ {
+ m_aAbsURIRef.append(':');
+ OUStringBuffer aNewAuth;
+ encodeText(aNewAuth, rThePassword, PART_USER_PASSWORD,
+ EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, false);
+ m_aAuth.set(m_aAbsURIRef, aNewAuth, m_aAbsURIRef.getLength());
+ bUserInfo = true;
+ }
+ else
+ {
+ setInvalid();
+ return false;
+ }
+ }
+ if (bUserInfo && getSchemeInfo().m_bHost)
+ m_aAbsURIRef.append('@');
+ if (getSchemeInfo().m_bHost)
+ {
+ OUStringBuffer aSynHost(rTheHost);
+ bool bNetBiosName = false;
+ switch (m_eScheme)
+ {
+ case INetProtocol::File:
+ {
+ if (OUString::unacquired(aSynHost).equalsIgnoreAsciiCase( "localhost" ))
+ {
+ aSynHost.setLength(0);
+ }
+ bNetBiosName = true;
+ }
+ break;
+
+ case INetProtocol::Ldap:
+ if (aSynHost.isEmpty() && nThePort != 0)
+ {
+ setInvalid();
+ return false;
+ }
+ break;
+
+ default:
+ if (aSynHost.isEmpty())
+ {
+ setInvalid();
+ return false;
+ }
+ break;
+ }
+ if (!parseHostOrNetBiosName(
+ aSynHost.getStr(), aSynHost.getStr() + aSynHost.getLength(),
+ EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, bNetBiosName, &aSynHost))
+ {
+ setInvalid();
+ return false;
+ }
+ m_aHost.set(m_aAbsURIRef, aSynHost, m_aAbsURIRef.getLength());
+ if (nThePort != 0)
+ {
+ if (getSchemeInfo().m_bPort)
+ {
+ m_aAbsURIRef.append(':');
+ m_aPort.set(m_aAbsURIRef,
+ OUString::number(nThePort),
+ m_aAbsURIRef.getLength());
+ }
+ else
+ {
+ setInvalid();
+ return false;
+ }
+ }
+ }
+ else if (!rTheHost.empty() || nThePort != 0)
+ {
+ setInvalid();
+ return false;
+ }
+ }
+ OUStringBuffer aSynPath(256);
+ sal_Unicode const * p = rThePath.data();
+ sal_Unicode const * pEnd = p + rThePath.size();
+ if (!parsePath(m_eScheme, &p, pEnd, EncodeMechanism::WasEncoded, RTL_TEXTENCODING_UTF8, false, '/',
+ 0x80000000, 0x80000000, 0x80000000, aSynPath)
+ || p != pEnd)
+ {
+ setInvalid();
+ return false;
+ }
+ m_aPath.set(m_aAbsURIRef, aSynPath, m_aAbsURIRef.getLength());
+ return true;
+}
+
+// static
+OUString INetURLObject::GetAbsURL(std::u16string_view rTheBaseURIRef,
+ OUString const & rTheRelURIRef,
+ EncodeMechanism eEncodeMechanism,
+ DecodeMechanism eDecodeMechanism,
+ rtl_TextEncoding eCharset)
+{
+ // Backwards compatibility:
+ if (rTheRelURIRef.isEmpty() || rTheRelURIRef[0] == '#')
+ return rTheRelURIRef;
+
+ INetURLObject aTheAbsURIRef;
+ bool bWasAbsolute;
+ return INetURLObject(rTheBaseURIRef, eEncodeMechanism, eCharset).
+ convertRelToAbs(rTheRelURIRef, aTheAbsURIRef,
+ bWasAbsolute, eEncodeMechanism,
+ eCharset, false, false,
+ false, FSysStyle::Detect)
+ || eEncodeMechanism != EncodeMechanism::WasEncoded
+ || eDecodeMechanism != DecodeMechanism::ToIUri
+ || eCharset != RTL_TEXTENCODING_UTF8 ?
+ aTheAbsURIRef.GetMainURL(eDecodeMechanism, eCharset) :
+ rTheRelURIRef;
+}
+
+OUString INetURLObject::getExternalURL() const
+{
+ OUString aTheExtURIRef;
+ translateToExternal(
+ m_aAbsURIRef, aTheExtURIRef);
+ return aTheExtURIRef;
+}
+
+bool INetURLObject::isSchemeEqualTo(std::u16string_view scheme) const {
+ return m_aScheme.isPresent()
+ && (rtl_ustr_compareIgnoreAsciiCase_WithLength(
+ scheme.data(), scheme.size(),
+ m_aAbsURIRef.getStr() + m_aScheme.getBegin(),
+ m_aScheme.getLength())
+ == 0);
+}
+
+bool INetURLObject::isAnyKnownWebDAVScheme() const {
+ return ( isSchemeEqualTo( INetProtocol::Http ) ||
+ isSchemeEqualTo( INetProtocol::Https ) ||
+ isSchemeEqualTo( INetProtocol::VndSunStarWebdav ) ||
+ isSchemeEqualTo( u"vnd.sun.star.webdavs" ) ||
+ isSchemeEqualTo( u"webdav" ) ||
+ isSchemeEqualTo( u"webdavs" ));
+}
+
+// static
+OUString INetURLObject::GetScheme(INetProtocol eTheScheme)
+{
+ return OUString::createFromAscii(getSchemeInfo(eTheScheme).m_pPrefix);
+}
+
+// static
+const OUString & INetURLObject::GetSchemeName(INetProtocol eTheScheme)
+{
+ return getSchemeInfo(eTheScheme).m_sScheme;
+}
+
+// static
+INetProtocol INetURLObject::CompareProtocolScheme(std::u16string_view aTheAbsURIRef)
+{
+ sal_Unicode const * p = aTheAbsURIRef.data();
+ PrefixInfo const * pPrefix = getPrefix(p, p + aTheAbsURIRef.size());
+ return pPrefix ? pPrefix->m_eScheme : INetProtocol::NotValid;
+}
+
+OUString INetURLObject::GetHostPort(DecodeMechanism eMechanism,
+ rtl_TextEncoding eCharset) const
+{
+ // Check because PROT_VND_SUN_STAR_HELP, PROT_VND_SUN_STAR_HIER, and
+ // PROT_VND_SUN_STAR_PKG misuse m_aHost:
+ if (!getSchemeInfo().m_bHost)
+ return OUString();
+ OUStringBuffer aHostPort(decode(m_aHost, eMechanism, eCharset));
+ if (m_aPort.isPresent())
+ {
+ aHostPort.append(":" + decode(m_aPort, eMechanism, eCharset));
+ }
+ return aHostPort.makeStringAndClear();
+}
+
+sal_uInt32 INetURLObject::GetPort() const
+{
+ if (m_aPort.isPresent())
+ {
+ sal_Unicode const * p = m_aAbsURIRef.getStr() + m_aPort.getBegin();
+ sal_Unicode const * pEnd = p + m_aPort.getLength();
+ sal_uInt32 nThePort;
+ if (INetMIME::scanUnsigned(p, pEnd, true, nThePort) && p == pEnd)
+ return nThePort;
+ }
+ return 0;
+}
+
+bool INetURLObject::SetPort(sal_uInt32 nThePort)
+{
+ if (getSchemeInfo().m_bPort && m_aHost.isPresent())
+ {
+ sal_Int32 nDelta;
+ if (m_aPort.isPresent())
+ nDelta = m_aPort.set(m_aAbsURIRef, OUString::number(nThePort));
+ else
+ {
+ m_aAbsURIRef.insert(m_aHost.getEnd(), u':');
+ nDelta = m_aPort.set(m_aAbsURIRef, OUString::number(nThePort), m_aHost.getEnd() + 1)
+ + 1;
+ }
+ m_aPath += nDelta;
+ m_aQuery += nDelta;
+ m_aFragment += nDelta;
+ return true;
+ }
+ return false;
+}
+
+sal_Int32 INetURLObject::getSegmentCount(bool bIgnoreFinalSlash) const
+{
+ if (!checkHierarchical())
+ return 0;
+
+ sal_Unicode const * p = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pEnd = p + m_aPath.getLength();
+ if (bIgnoreFinalSlash && pEnd > p && pEnd[-1] == '/')
+ --pEnd;
+ sal_Int32 n = p == pEnd || *p == '/' ? 0 : 1;
+ while (p != pEnd)
+ if (*p++ == '/')
+ ++n;
+ return n;
+}
+
+bool INetURLObject::removeSegment(sal_Int32 nIndex, bool bIgnoreFinalSlash)
+{
+ SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash));
+ if (!aSegment.isPresent())
+ return false;
+
+ OUStringBuffer aNewPath(m_aPath.getLength());
+ aNewPath.append(m_aAbsURIRef.getStr() + m_aPath.getBegin(),
+ aSegment.getBegin() - m_aPath.getBegin());
+ if (bIgnoreFinalSlash && aSegment.getEnd() == m_aPath.getEnd())
+ aNewPath.append('/');
+ else
+ aNewPath.append(m_aAbsURIRef.getStr() + aSegment.getEnd(),
+ m_aPath.getEnd() - aSegment.getEnd());
+ if (aNewPath.isEmpty() && !aSegment.isEmpty() &&
+ m_aAbsURIRef[aSegment.getBegin()] == '/')
+ {
+ aNewPath.append('/');
+ }
+
+ return setPath(aNewPath, EncodeMechanism::NotCanonical,
+ RTL_TEXTENCODING_UTF8);
+}
+
+OUString INetURLObject::getName(sal_Int32 nIndex, bool bIgnoreFinalSlash,
+ DecodeMechanism eMechanism,
+ rtl_TextEncoding eCharset) const
+{
+ SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash));
+ if (!aSegment.isPresent())
+ return OUString();
+
+ sal_Unicode const * pSegBegin
+ = m_aAbsURIRef.getStr() + aSegment.getBegin();
+ sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength();
+
+ if (pSegBegin < pSegEnd && *pSegBegin == '/')
+ ++pSegBegin;
+ sal_Unicode const * p = pSegBegin;
+ while (p != pSegEnd && *p != ';')
+ ++p;
+
+ return decode(pSegBegin, p, eMechanism, eCharset);
+}
+
+bool INetURLObject::setName(std::u16string_view rTheName, EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset)
+{
+ SubString aSegment(getSegment(LAST_SEGMENT, true));
+ if (!aSegment.isPresent())
+ return false;
+
+ sal_Unicode const * pPathBegin
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength();
+ sal_Unicode const * pSegBegin
+ = m_aAbsURIRef.getStr() + aSegment.getBegin();
+ sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength();
+
+ if (pSegBegin < pSegEnd && *pSegBegin == '/')
+ ++pSegBegin;
+ sal_Unicode const * p = pSegBegin;
+ while (p != pSegEnd && *p != ';')
+ ++p;
+
+ OUStringBuffer aNewPath(256);
+ aNewPath.append(std::u16string_view(pPathBegin, pSegBegin - pPathBegin));
+ encodeText(aNewPath, rTheName, PART_PCHAR, eMechanism, eCharset, true);
+ aNewPath.append(std::u16string_view(p, pPathEnd - p));
+ return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8);
+}
+
+bool INetURLObject::hasExtension()
+ const
+{
+ SubString aSegment(getSegment(LAST_SEGMENT, true/*bIgnoreFinalSlash*/));
+ if (!aSegment.isPresent())
+ return false;
+
+ sal_Unicode const * pSegBegin
+ = m_aAbsURIRef.getStr() + aSegment.getBegin();
+ sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength();
+
+ if (pSegBegin < pSegEnd && *pSegBegin == '/')
+ ++pSegBegin;
+ for (sal_Unicode const * p = pSegBegin; p != pSegEnd && *p != ';'; ++p)
+ if (*p == '.' && p != pSegBegin)
+ return true;
+ return false;
+}
+
+OUString INetURLObject::getBase(sal_Int32 nIndex, bool bIgnoreFinalSlash,
+ DecodeMechanism eMechanism,
+ rtl_TextEncoding eCharset) const
+{
+ SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash));
+ if (!aSegment.isPresent())
+ return OUString();
+
+ sal_Unicode const * pSegBegin
+ = m_aAbsURIRef.getStr() + aSegment.getBegin();
+ sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength();
+
+ if (pSegBegin < pSegEnd && *pSegBegin == '/')
+ ++pSegBegin;
+ sal_Unicode const * pExtension = nullptr;
+ sal_Unicode const * p = pSegBegin;
+ for (; p != pSegEnd && *p != ';'; ++p)
+ if (*p == '.' && p != pSegBegin)
+ pExtension = p;
+ if (!pExtension)
+ pExtension = p;
+
+ return decode(pSegBegin, pExtension, eMechanism, eCharset);
+}
+
+bool INetURLObject::setBase(std::u16string_view rTheBase, sal_Int32 nIndex,
+ EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset)
+{
+ SubString aSegment(getSegment(nIndex, true/*bIgnoreFinalSlash*/));
+ if (!aSegment.isPresent())
+ return false;
+
+ sal_Unicode const * pPathBegin
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength();
+ sal_Unicode const * pSegBegin
+ = m_aAbsURIRef.getStr() + aSegment.getBegin();
+ sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength();
+
+ if (pSegBegin < pSegEnd && *pSegBegin == '/')
+ ++pSegBegin;
+ sal_Unicode const * pExtension = nullptr;
+ sal_Unicode const * p = pSegBegin;
+ for (; p != pSegEnd && *p != ';'; ++p)
+ if (*p == '.' && p != pSegBegin)
+ pExtension = p;
+ if (!pExtension)
+ pExtension = p;
+
+ OUStringBuffer aNewPath(256);
+ aNewPath.append(std::u16string_view(pPathBegin, pSegBegin - pPathBegin));
+ encodeText(aNewPath, rTheBase, PART_PCHAR, eMechanism, eCharset, true);
+ aNewPath.append(std::u16string_view(pExtension, pPathEnd - pExtension));
+ return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8);
+}
+
+OUString INetURLObject::getExtension(sal_Int32 nIndex,
+ bool bIgnoreFinalSlash,
+ DecodeMechanism eMechanism,
+ rtl_TextEncoding eCharset) const
+{
+ SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash));
+ if (!aSegment.isPresent())
+ return OUString();
+
+ sal_Unicode const * pSegBegin
+ = m_aAbsURIRef.getStr() + aSegment.getBegin();
+ sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength();
+
+ if (pSegBegin < pSegEnd && *pSegBegin == '/')
+ ++pSegBegin;
+ sal_Unicode const * pExtension = nullptr;
+ sal_Unicode const * p = pSegBegin;
+ for (; p != pSegEnd && *p != ';'; ++p)
+ if (*p == '.' && p != pSegBegin)
+ pExtension = p;
+
+ if (!pExtension)
+ return OUString();
+
+ return decode(pExtension + 1, p, eMechanism, eCharset);
+}
+
+bool INetURLObject::setExtension(std::u16string_view rTheExtension,
+ sal_Int32 nIndex, bool bIgnoreFinalSlash,
+ rtl_TextEncoding eCharset)
+{
+ SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash));
+ if (!aSegment.isPresent())
+ return false;
+
+ sal_Unicode const * pPathBegin
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength();
+ sal_Unicode const * pSegBegin
+ = m_aAbsURIRef.getStr() + aSegment.getBegin();
+ sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength();
+
+ if (pSegBegin < pSegEnd && *pSegBegin == '/')
+ ++pSegBegin;
+ sal_Unicode const * pExtension = nullptr;
+ sal_Unicode const * p = pSegBegin;
+ for (; p != pSegEnd && *p != ';'; ++p)
+ if (*p == '.' && p != pSegBegin)
+ pExtension = p;
+ if (!pExtension)
+ pExtension = p;
+
+ OUStringBuffer aNewPath(256);
+ aNewPath.append(OUString::Concat(std::u16string_view(pPathBegin, pExtension - pPathBegin)) + ".");
+ encodeText(aNewPath, rTheExtension, PART_PCHAR, EncodeMechanism::WasEncoded, eCharset, true);
+ aNewPath.append(std::u16string_view(p, pPathEnd - p));
+ return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8);
+}
+
+bool INetURLObject::removeExtension(sal_Int32 nIndex, bool bIgnoreFinalSlash)
+{
+ SubString aSegment(getSegment(nIndex, bIgnoreFinalSlash));
+ if (!aSegment.isPresent())
+ return false;
+
+ sal_Unicode const * pPathBegin
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength();
+ sal_Unicode const * pSegBegin
+ = m_aAbsURIRef.getStr() + aSegment.getBegin();
+ sal_Unicode const * pSegEnd = pSegBegin + aSegment.getLength();
+
+ if (pSegBegin < pSegEnd && *pSegBegin == '/')
+ ++pSegBegin;
+ sal_Unicode const * pExtension = nullptr;
+ sal_Unicode const * p = pSegBegin;
+ for (; p != pSegEnd && *p != ';'; ++p)
+ if (*p == '.' && p != pSegBegin)
+ pExtension = p;
+ if (!pExtension)
+ return true;
+
+ OUString aNewPath =
+ OUString::Concat(std::u16string_view(pPathBegin, pExtension - pPathBegin)) +
+ std::u16string_view(p, pPathEnd - p);
+
+ return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8);
+}
+
+bool INetURLObject::hasFinalSlash() const
+{
+ if (!checkHierarchical())
+ return false;
+
+ sal_Unicode const * pPathBegin
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength();
+ return pPathEnd > pPathBegin && pPathEnd[-1] == '/';
+}
+
+bool INetURLObject::setFinalSlash()
+{
+ if (!checkHierarchical())
+ return false;
+
+ sal_Unicode const * pPathBegin
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength();
+ if (pPathEnd > pPathBegin && pPathEnd[-1] == '/')
+ return true;
+
+ OUString aNewPath
+ = OUString::Concat(std::u16string_view(pPathBegin, pPathEnd - pPathBegin)) + "/";
+
+ return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8);
+}
+
+bool INetURLObject::removeFinalSlash()
+{
+ if (!checkHierarchical())
+ return false;
+
+ sal_Unicode const * pPathBegin
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pPathEnd = pPathBegin + m_aPath.getLength();
+ if (pPathEnd <= pPathBegin || pPathEnd[-1] != '/')
+ return true;
+
+ --pPathEnd;
+ if (pPathEnd == pPathBegin && *pPathBegin == '/')
+ return false;
+ OUString aNewPath(pPathBegin, pPathEnd - pPathBegin);
+
+ return setPath(aNewPath, EncodeMechanism::NotCanonical, RTL_TEXTENCODING_UTF8);
+}
+
+OUString INetURLObject::getFSysPath(FSysStyle eStyle,
+ sal_Unicode * pDelimiter) const
+{
+ if (m_eScheme != INetProtocol::File)
+ return OUString();
+
+ if (((eStyle & FSysStyle::Vos) ? 1 : 0)
+ + ((eStyle & FSysStyle::Unix) ? 1 : 0)
+ + ((eStyle & FSysStyle::Dos) ? 1 : 0)
+ > 1)
+ {
+ if(eStyle & FSysStyle::Vos && m_aHost.isPresent() && m_aHost.getLength() > 0)
+ {
+ eStyle= FSysStyle::Vos;
+ }
+ else
+ {
+ if(hasDosVolume(eStyle) || ((eStyle & FSysStyle::Dos) && m_aHost.isPresent() && m_aHost.getLength() > 0))
+ {
+ eStyle = FSysStyle::Dos;
+ }
+ else
+ {
+ if(eStyle & FSysStyle::Unix && (!m_aHost.isPresent() || m_aHost.getLength() == 0))
+ {
+ eStyle = FSysStyle::Unix;
+ }
+ else
+ {
+ eStyle= FSysStyle(0);
+ }
+ }
+ }
+ }
+
+ switch (eStyle)
+ {
+ case FSysStyle::Vos:
+ {
+ if (pDelimiter)
+ *pDelimiter = '/';
+
+ OUStringBuffer aSynFSysPath("//");
+ if (m_aHost.isPresent() && m_aHost.getLength() > 0)
+ aSynFSysPath.append(decode(m_aHost, DecodeMechanism::WithCharset,
+ RTL_TEXTENCODING_UTF8));
+ else
+ aSynFSysPath.append('.');
+ aSynFSysPath.append(decode(m_aPath, DecodeMechanism::WithCharset,
+ RTL_TEXTENCODING_UTF8));
+ return aSynFSysPath.makeStringAndClear();
+ }
+
+ case FSysStyle::Unix:
+ {
+ if (m_aHost.isPresent() && m_aHost.getLength() > 0)
+ return OUString();
+
+ if (pDelimiter)
+ *pDelimiter = '/';
+
+ return decode(m_aPath, DecodeMechanism::WithCharset, RTL_TEXTENCODING_UTF8);
+ }
+
+ case FSysStyle::Dos:
+ {
+ if (pDelimiter)
+ *pDelimiter = '\\';
+
+ OUStringBuffer aSynFSysPath(64);
+ if (m_aHost.isPresent() && m_aHost.getLength() > 0)
+ {
+ aSynFSysPath.append("\\\\"
+ + decode(m_aHost, DecodeMechanism::WithCharset, RTL_TEXTENCODING_UTF8)
+ + "\\");
+ }
+ sal_Unicode const * p
+ = m_aAbsURIRef.getStr() + m_aPath.getBegin();
+ sal_Unicode const * pEnd = p + m_aPath.getLength();
+ DBG_ASSERT(p < pEnd && *p == '/',
+ "INetURLObject::getFSysPath(): Bad path");
+ ++p;
+ while (p < pEnd)
+ {
+ EscapeType eEscapeType;
+ sal_uInt32 nUTF32 = getUTF32(p, pEnd, EncodeMechanism::WasEncoded,
+ RTL_TEXTENCODING_UTF8,
+ eEscapeType);
+ if (eEscapeType == EscapeType::NONE && nUTF32 == '/')
+ aSynFSysPath.append('\\');
+ else
+ aSynFSysPath.appendUtf32(nUTF32);
+ }
+ return aSynFSysPath.makeStringAndClear();
+ }
+
+ default:
+ return OUString();
+ }
+}
+
+// static
+void INetURLObject::appendUCS4Escape(OUStringBuffer & rTheText,
+ sal_uInt32 nUCS4)
+{
+ DBG_ASSERT(nUCS4 < 0x80000000,
+ "INetURLObject::appendUCS4Escape(): Bad char");
+ if (nUCS4 < 0x80)
+ appendEscape(rTheText, nUCS4);
+ else if (nUCS4 < 0x800)
+ {
+ appendEscape(rTheText, nUCS4 >> 6 | 0xC0);
+ appendEscape(rTheText, (nUCS4 & 0x3F) | 0x80);
+ }
+ else if (nUCS4 < 0x10000)
+ {
+ appendEscape(rTheText, nUCS4 >> 12 | 0xE0);
+ appendEscape(rTheText, (nUCS4 >> 6 & 0x3F) | 0x80);
+ appendEscape(rTheText, (nUCS4 & 0x3F) | 0x80);
+ }
+ else if (nUCS4 < 0x200000)
+ {
+ appendEscape(rTheText, nUCS4 >> 18 | 0xF0);
+ appendEscape(rTheText, (nUCS4 >> 12 & 0x3F) | 0x80);
+ appendEscape(rTheText, (nUCS4 >> 6 & 0x3F) | 0x80);
+ appendEscape(rTheText, (nUCS4 & 0x3F) | 0x80);
+ }
+ else if (nUCS4 < 0x4000000)
+ {
+ appendEscape(rTheText, nUCS4 >> 24 | 0xF8);
+ appendEscape(rTheText, (nUCS4 >> 18 & 0x3F) | 0x80);
+ appendEscape(rTheText, (nUCS4 >> 12 & 0x3F) | 0x80);
+ appendEscape(rTheText, (nUCS4 >> 6 & 0x3F) | 0x80);
+ appendEscape(rTheText, (nUCS4 & 0x3F) | 0x80);
+ }
+ else
+ {
+ appendEscape(rTheText, nUCS4 >> 30 | 0xFC);
+ appendEscape(rTheText, (nUCS4 >> 24 & 0x3F) | 0x80);
+ appendEscape(rTheText, (nUCS4 >> 18 & 0x3F) | 0x80);
+ appendEscape(rTheText, (nUCS4 >> 12 & 0x3F) | 0x80);
+ appendEscape(rTheText, (nUCS4 >> 6 & 0x3F) | 0x80);
+ appendEscape(rTheText, (nUCS4 & 0x3F) | 0x80);
+ }
+}
+
+// static
+void INetURLObject::appendUCS4(OUStringBuffer& rTheText, sal_uInt32 nUCS4,
+ EscapeType eEscapeType,
+ Part ePart, rtl_TextEncoding eCharset,
+ bool bKeepVisibleEscapes)
+{
+ bool bEscape;
+ rtl_TextEncoding eTargetCharset = RTL_TEXTENCODING_DONTKNOW;
+ switch (eEscapeType)
+ {
+ case EscapeType::NONE:
+ if (mustEncode(nUCS4, ePart))
+ {
+ bEscape = true;
+ eTargetCharset = RTL_TEXTENCODING_UTF8;
+ }
+ else
+ bEscape = false;
+ break;
+
+ case EscapeType::Octet:
+ bEscape = true;
+ eTargetCharset = RTL_TEXTENCODING_ISO_8859_1;
+ break;
+
+ case EscapeType::Utf32:
+ if (mustEncode(nUCS4, ePart))
+ {
+ bEscape = true;
+ eTargetCharset = eCharset;
+ }
+ else if (bKeepVisibleEscapes && INetMIME::isVisible(nUCS4))
+ {
+ bEscape = true;
+ eTargetCharset = RTL_TEXTENCODING_ASCII_US;
+ }
+ else
+ bEscape = false;
+ break;
+ default:
+ bEscape = false;
+ }
+
+ if (bEscape)
+ {
+ switch (eTargetCharset)
+ {
+ default:
+ OSL_FAIL("INetURLObject::appendUCS4(): Unsupported charset");
+ [[fallthrough]];
+ case RTL_TEXTENCODING_ASCII_US:
+ case RTL_TEXTENCODING_ISO_8859_1:
+ appendEscape(rTheText, nUCS4);
+ break;
+ case RTL_TEXTENCODING_UTF8:
+ appendUCS4Escape(rTheText, nUCS4);
+ break;
+ }
+ }
+ else
+ rTheText.append(sal_Unicode(nUCS4));
+}
+
+// static
+sal_uInt32 INetURLObject::getUTF32(sal_Unicode const *& rBegin,
+ sal_Unicode const * pEnd,
+ EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset,
+ EscapeType & rEscapeType)
+{
+ DBG_ASSERT(rBegin < pEnd, "INetURLObject::getUTF32(): Bad sequence");
+ sal_uInt32 nUTF32 = INetMIME::getUTF32Character(rBegin, pEnd);
+ switch (eMechanism)
+ {
+ case EncodeMechanism::All:
+ rEscapeType = EscapeType::NONE;
+ break;
+
+ case EncodeMechanism::WasEncoded:
+ {
+ int nWeight1;
+ int nWeight2;
+ if (nUTF32 == static_cast<unsigned char>('%') && rBegin + 1 < pEnd
+ && (nWeight1 = INetMIME::getHexWeight(rBegin[0])) >= 0
+ && (nWeight2 = INetMIME::getHexWeight(rBegin[1])) >= 0)
+ {
+ rBegin += 2;
+ nUTF32 = nWeight1 << 4 | nWeight2;
+ switch (eCharset)
+ {
+ default:
+ OSL_FAIL(
+ "INetURLObject::getUTF32(): Unsupported charset");
+ [[fallthrough]];
+ case RTL_TEXTENCODING_ASCII_US:
+ rEscapeType = rtl::isAscii(nUTF32) ?
+ EscapeType::Utf32 : EscapeType::Octet;
+ break;
+
+ case RTL_TEXTENCODING_ISO_8859_1:
+ rEscapeType = EscapeType::Utf32;
+ break;
+
+ case RTL_TEXTENCODING_UTF8:
+ if (rtl::isAscii(nUTF32))
+ rEscapeType = EscapeType::Utf32;
+ else
+ {
+ if (nUTF32 >= 0xC0 && nUTF32 <= 0xF4)
+ {
+ sal_uInt32 nEncoded;
+ int nShift;
+ sal_uInt32 nMin;
+ if (nUTF32 <= 0xDF)
+ {
+ nEncoded = (nUTF32 & 0x1F) << 6;
+ nShift = 0;
+ nMin = 0x80;
+ }
+ else if (nUTF32 <= 0xEF)
+ {
+ nEncoded = (nUTF32 & 0x0F) << 12;
+ nShift = 6;
+ nMin = 0x800;
+ }
+ else
+ {
+ nEncoded = (nUTF32 & 0x07) << 18;
+ nShift = 12;
+ nMin = 0x10000;
+ }
+ sal_Unicode const * p = rBegin;
+ bool bUTF8 = true;
+ for (;;)
+ {
+ if (pEnd - p < 3
+ || p[0] != '%'
+ || (nWeight1
+ = INetMIME::getHexWeight(p[1]))
+ < 8
+ || nWeight1 > 11
+ || (nWeight2
+ = INetMIME::getHexWeight(p[2]))
+ < 0)
+ {
+ bUTF8 = false;
+ break;
+ }
+ p += 3;
+ nEncoded
+ |= ((nWeight1 & 3) << 4 | nWeight2)
+ << nShift;
+ if (nShift == 0)
+ break;
+ nShift -= 6;
+ }
+ if (bUTF8 && rtl::isUnicodeScalarValue(nEncoded)
+ && nEncoded >= nMin)
+ {
+ rBegin = p;
+ nUTF32 = nEncoded;
+ rEscapeType = EscapeType::Utf32;
+ break;
+ }
+ }
+ rEscapeType = EscapeType::Octet;
+ }
+ break;
+ }
+ }
+ else
+ rEscapeType = EscapeType::NONE;
+ break;
+ }
+
+ case EncodeMechanism::NotCanonical:
+ {
+ int nWeight1;
+ int nWeight2;
+ if (nUTF32 == static_cast<unsigned char>('%') && rBegin + 1 < pEnd
+ && ((nWeight1 = INetMIME::getHexWeight(rBegin[0])) >= 0)
+ && ((nWeight2 = INetMIME::getHexWeight(rBegin[1])) >= 0))
+ {
+ rBegin += 2;
+ nUTF32 = nWeight1 << 4 | nWeight2;
+ rEscapeType = EscapeType::Octet;
+ }
+ else
+ rEscapeType = EscapeType::NONE;
+ break;
+ }
+ }
+ return nUTF32;
+}
+
+// static
+sal_uInt32 INetURLObject::scanDomain(sal_Unicode const *& rBegin,
+ sal_Unicode const * pEnd,
+ bool bEager)
+{
+ enum State { STATE_DOT, STATE_LABEL, STATE_HYPHEN };
+ State eState = STATE_DOT;
+ sal_Int32 nLabels = 0;
+ sal_Unicode const * pLastAlphanumeric = nullptr;
+ for (sal_Unicode const * p = rBegin;; ++p)
+ switch (eState)
+ {
+ case STATE_DOT:
+ if (p != pEnd && (rtl::isAsciiAlphanumeric(*p) || *p == '_'))
+ {
+ ++nLabels;
+ eState = STATE_LABEL;
+ break;
+ }
+ if (bEager || nLabels == 0)
+ return 0;
+ rBegin = p - 1;
+ return nLabels;
+
+ case STATE_LABEL:
+ if (p != pEnd)
+ {
+ if (rtl::isAsciiAlphanumeric(*p) || *p == '_')
+ break;
+ else if (*p == '.')
+ {
+ eState = STATE_DOT;
+ break;
+ }
+ else if (*p == '-')
+ {
+ pLastAlphanumeric = p;
+ eState = STATE_HYPHEN;
+ break;
+ }
+ }
+ rBegin = p;
+ return nLabels;
+
+ case STATE_HYPHEN:
+ if (p != pEnd)
+ {
+ if (rtl::isAsciiAlphanumeric(*p) || *p == '_')
+ {
+ eState = STATE_LABEL;
+ break;
+ }
+ else if (*p == '-')
+ break;
+ }
+ if (bEager)
+ return 0;
+ rBegin = pLastAlphanumeric;
+ return nLabels;
+ }
+}
+
+// static
+bool INetURLObject::scanIPv6reference(sal_Unicode const *& rBegin,
+ sal_Unicode const * pEnd)
+{
+ if (rBegin != pEnd && *rBegin == '[') {
+ sal_Unicode const * p = rBegin + 1;
+ //TODO: check for valid IPv6address (RFC 2373):
+ while (p != pEnd && (rtl::isAsciiHexDigit(*p) || *p == ':' || *p == '.'))
+ {
+ ++p;
+ }
+ if (p != pEnd && *p == ']') {
+ rBegin = p + 1;
+ return true;
+ }
+ }
+ return false;
+}
+
+OUString INetURLObject::GetPartBeforeLastName()
+ const
+{
+ if (!checkHierarchical())
+ return OUString();
+ INetURLObject aTemp(*this);
+ aTemp.clearFragment();
+ aTemp.clearQuery();
+ aTemp.removeSegment(LAST_SEGMENT, false);
+ aTemp.setFinalSlash();
+ return aTemp.GetMainURL(DecodeMechanism::ToIUri);
+}
+
+OUString INetURLObject::GetLastName(DecodeMechanism eMechanism,
+ rtl_TextEncoding eCharset) const
+{
+ return getName(LAST_SEGMENT, true, eMechanism, eCharset);
+}
+
+OUString INetURLObject::GetFileExtension() const
+{
+ return getExtension(LAST_SEGMENT, false);
+}
+
+void INetURLObject::CutLastName()
+{
+ INetURLObject aTemp(*this);
+ aTemp.clearFragment();
+ aTemp.clearQuery();
+ if (!aTemp.removeSegment(LAST_SEGMENT, false))
+ return;
+ *this = aTemp;
+}
+
+OUString INetURLObject::PathToFileName() const
+{
+ if (m_eScheme != INetProtocol::File)
+ return OUString();
+ OUString aSystemPath;
+ if (osl::FileBase::getSystemPathFromFileURL(
+ decode(m_aAbsURIRef.getStr(),
+ m_aAbsURIRef.getStr() + m_aPath.getEnd(),
+ DecodeMechanism::NONE, RTL_TEXTENCODING_UTF8),
+ aSystemPath)
+ != osl::FileBase::E_None)
+ return OUString();
+ return aSystemPath;
+}
+
+OUString INetURLObject::GetFull() const
+{
+ INetURLObject aTemp(*this);
+ aTemp.removeFinalSlash();
+ return aTemp.PathToFileName();
+}
+
+OUString INetURLObject::GetPath() const
+{
+ INetURLObject aTemp(*this);
+ aTemp.removeSegment();
+ aTemp.removeFinalSlash();
+ return aTemp.PathToFileName();
+}
+
+void INetURLObject::SetBase(std::u16string_view rTheBase)
+{
+ setBase(rTheBase, LAST_SEGMENT, EncodeMechanism::All);
+}
+
+OUString INetURLObject::GetBase() const
+{
+ return getBase(LAST_SEGMENT, true, DecodeMechanism::WithCharset);
+}
+
+void INetURLObject::SetExtension(std::u16string_view rTheExtension)
+{
+ setExtension(rTheExtension, LAST_SEGMENT, false);
+}
+
+OUString INetURLObject::CutExtension()
+{
+ OUString aTheExtension(getExtension(LAST_SEGMENT, false));
+ return removeExtension(LAST_SEGMENT, false)
+ ? aTheExtension : OUString();
+}
+
+bool INetURLObject::IsExoticProtocol() const
+{
+ return m_eScheme == INetProtocol::Slot ||
+ m_eScheme == INetProtocol::Macro ||
+ m_eScheme == INetProtocol::Uno ||
+ isSchemeEqualTo(u"vnd.sun.star.script") ||
+ isSchemeEqualTo(u"service");
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/fsys/wldcrd.cxx b/tools/source/fsys/wldcrd.cxx
new file mode 100644
index 0000000000..e8199975c6
--- /dev/null
+++ b/tools/source/fsys/wldcrd.cxx
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/wldcrd.hxx>
+
+/** Tests, whether a wildcard in pWild will match for pStr.
+ *
+ * '*' in pWild means n chars for n > 0.
+ * '?' in pWild mean match exactly one character.
+ *
+ */
+bool WildCard::ImpMatch( std::u16string_view aWild, std::u16string_view aStr )
+{
+ const sal_Unicode* pPosAfterAsterisk = nullptr;
+ const sal_Unicode* pWild = aWild.data();
+ const sal_Unicode* pWildEnd = aWild.data() + aWild.size();
+ const sal_Unicode* pStr = aStr.data();
+ const sal_Unicode* pStrEnd = aStr.data() + aStr.size();
+
+ while (pWild != pWildEnd)
+ {
+ switch (*pWild)
+ {
+ case '?':
+ if ( pStr == pStrEnd )
+ return false;
+ break; // Match -> proceed to the next character
+ case '\\': // Escaping '?' and '*'; don't we need to escape '\\'?
+ if ((pWild + 1 != pWildEnd) && ((*(pWild + 1) == '?') || (*(pWild + 1) == '*')))
+ pWild++;
+ [[fallthrough]];
+ default: // No wildcard, literal match
+ if (pStr == pStrEnd)
+ return false;
+ if (*pWild == *pStr)
+ break; // Match -> proceed to the next character
+ if (!pPosAfterAsterisk)
+ return false;
+ pWild = pPosAfterAsterisk;
+ [[fallthrough]];
+ case '*':
+ while ( pWild != pWildEnd && *pWild == '*' )
+ pWild++;
+ if ( pWild == pWildEnd )
+ return true;
+ // Consider strange things like "**?*?*"
+ while (*pWild == '?')
+ {
+ if (pStr == pStrEnd)
+ return false;
+ pWild++;
+ pStr++;
+ while (pWild != pWildEnd && *pWild == '*')
+ pWild++;
+ if (pWild == pWildEnd)
+ return true;
+ }
+ // At this point, we are past wildcards, and a literal match must follow
+ if ( pStr == pStrEnd )
+ return false;
+ pPosAfterAsterisk = pWild;
+ if ((*pWild == '\\') && (pWild + 1 != pWildEnd) && ((*(pWild + 1) == '?') || (*(pWild + 1) == '*')))
+ pWild++;
+ while (*pStr != *pWild)
+ {
+ pStr++;
+ if ( pStr == pStrEnd )
+ return false;
+ }
+ break; // Match -> proceed to the next character
+ }
+ // We arrive here when the current characters in pWild and pStr match
+ assert(pWild != pWildEnd);
+ pWild++;
+ assert(pStr != pStrEnd);
+ pStr++;
+ if (pWild == pWildEnd && pPosAfterAsterisk && pStr != pStrEnd)
+ pWild = pPosAfterAsterisk; // Try again on the rest of pStr
+ }
+ assert(pWild == pWildEnd);
+ return pStr == pStrEnd;
+}
+
+bool WildCard::Matches( std::u16string_view rString ) const
+{
+ std::u16string_view aTmpWild = aWildString;
+
+ size_t nSepPos;
+
+ if ( cSepSymbol != '\0' )
+ {
+ while ( (nSepPos = aTmpWild.find(cSepSymbol)) != std::u16string_view::npos )
+ {
+ // Check all split wildcards
+ if ( ImpMatch( aTmpWild.substr( 0, nSepPos ), rString ) )
+ return true;
+ aTmpWild = aTmpWild.substr(nSepPos + 1); // remove separator
+ }
+ }
+
+ return ImpMatch( aTmpWild, rString );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/b3dtrans.cxx b/tools/source/generic/b3dtrans.cxx
new file mode 100644
index 0000000000..0cae750da0
--- /dev/null
+++ b/tools/source/generic/b3dtrans.cxx
@@ -0,0 +1,456 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/b3dtrans.hxx>
+
+#include <osl/diagnose.h>
+
+ // Near and far clipping planes
+constexpr double gfNearBound = 0.001;
+constexpr double gfFarBound = 1.001;
+
+
+// B3dTransformationSet --------------------------------------------------------
+// Transformations for all 3D output
+
+B3dTransformationSet::B3dTransformationSet()
+{
+ Reset();
+}
+
+B3dTransformationSet::~B3dTransformationSet()
+{
+}
+
+void B3dTransformationSet::Orientation(basegfx::B3DHomMatrix& rTarget, const basegfx::B3DPoint& aVRP, basegfx::B3DVector aVPN, basegfx::B3DVector aVUP)
+{
+ rTarget.translate( -aVRP.getX(), -aVRP.getY(), -aVRP.getZ());
+ aVUP.normalize();
+ aVPN.normalize();
+ basegfx::B3DVector aRx(aVUP);
+ basegfx::B3DVector aRy(aVPN);
+ aRx = aRx.getPerpendicular(aRy);
+ aRx.normalize();
+ aRy = aRy.getPerpendicular(aRx);
+ aRy.normalize();
+ basegfx::B3DHomMatrix aTemp;
+ aTemp.set(0, 0, aRx.getX());
+ aTemp.set(0, 1, aRx.getY());
+ aTemp.set(0, 2, aRx.getZ());
+ aTemp.set(1, 0, aRy.getX());
+ aTemp.set(1, 1, aRy.getY());
+ aTemp.set(1, 2, aRy.getZ());
+ aTemp.set(2, 0, aVPN.getX());
+ aTemp.set(2, 1, aVPN.getY());
+ aTemp.set(2, 2, aVPN.getZ());
+ rTarget *= aTemp;
+}
+
+void B3dTransformationSet::Frustum(basegfx::B3DHomMatrix& rTarget, double fLeft, double fRight, double fBottom, double fTop, double fNear, double fFar)
+{
+ if(!(fNear > 0.0))
+ {
+ fNear = 0.001;
+ }
+ if(!(fFar > 0.0))
+ {
+ fFar = 1.0;
+ }
+ if(fNear == fFar)
+ {
+ fFar = fNear + 1.0;
+ }
+ if(fLeft == fRight)
+ {
+ fLeft -= 1.0;
+ fRight += 1.0;
+ }
+ if(fTop == fBottom)
+ {
+ fBottom -= 1.0;
+ fTop += 1.0;
+ }
+ basegfx::B3DHomMatrix aTemp;
+
+ aTemp.set(0, 0, 2.0 * fNear / (fRight - fLeft));
+ aTemp.set(1, 1, 2.0 * fNear / (fTop - fBottom));
+ aTemp.set(0, 2, (fRight + fLeft) / (fRight - fLeft));
+ aTemp.set(1, 2, (fTop + fBottom) / (fTop - fBottom));
+ aTemp.set(2, 2, -1.0 * ((fFar + fNear) / (fFar - fNear)));
+ aTemp.set(3, 2, -1.0);
+ aTemp.set(2, 3, -1.0 * ((2.0 * fFar * fNear) / (fFar - fNear)));
+ aTemp.set(3, 3, 0.0);
+
+ rTarget *= aTemp;
+}
+
+void B3dTransformationSet::Ortho(basegfx::B3DHomMatrix& rTarget,
+ double fLeft, double fRight, double fBottom, double fTop,
+ double fNear, double fFar)
+{
+ if(fNear == fFar)
+ {
+ OSL_FAIL("Near and far clipping plane in Ortho definition are identical");
+ fFar = fNear + 1.0;
+ }
+ if(fLeft == fRight)
+ {
+ OSL_FAIL("Left and right in Ortho definition are identical");
+ fLeft -= 1.0;
+ fRight += 1.0;
+ }
+ if(fTop == fBottom)
+ {
+ OSL_FAIL("Top and bottom in Ortho definition are identical");
+ fBottom -= 1.0;
+ fTop += 1.0;
+ }
+ basegfx::B3DHomMatrix aTemp;
+
+ aTemp.set(0, 0, 2.0 / (fRight - fLeft));
+ aTemp.set(1, 1, 2.0 / (fTop - fBottom));
+ aTemp.set(2, 2, -1.0 * (2.0 / (fFar - fNear)));
+ aTemp.set(0, 3, -1.0 * ((fRight + fLeft) / (fRight - fLeft)));
+ aTemp.set(1, 3, -1.0 * ((fTop + fBottom) / (fTop - fBottom)));
+ aTemp.set(2, 3, -1.0 * ((fFar + fNear) / (fFar - fNear)));
+
+ rTarget *= aTemp;
+}
+
+/// reset values
+void B3dTransformationSet::Reset()
+{
+ // Reset matrices to identity matrices
+ maObjectTrans.identity();
+ PostSetObjectTrans();
+
+ Orientation(maOrientation);
+ PostSetOrientation();
+
+ maTexture.identity();
+
+ mfLeftBound = mfBottomBound = -1.0;
+ mfRightBound = mfTopBound = 1.0;
+
+ mfRatio = 0.0;
+
+ maViewportRectangle = tools::Rectangle(-1, -1, 2, 2);
+ maVisibleRectangle = maViewportRectangle;
+
+ mbPerspective = true;
+
+ mbProjectionValid = false;
+
+ CalcViewport();
+}
+
+/// Object transformation
+void B3dTransformationSet::PostSetObjectTrans()
+{
+ // Assign and compute inverse
+ maInvObjectTrans = maObjectTrans;
+ maInvObjectTrans.invert();
+}
+
+void B3dTransformationSet::SetOrientation(const basegfx::B3DPoint& rVRP, const basegfx::B3DVector& rVPN, const basegfx::B3DVector& rVUP)
+{
+ maOrientation.identity();
+ Orientation(maOrientation, rVRP, rVPN, rVUP);
+
+ PostSetOrientation();
+}
+
+void B3dTransformationSet::PostSetOrientation()
+{
+ // Assign and compute inverse
+ maInvOrientation = maOrientation;
+ maInvOrientation.invert();
+}
+
+/// Projections for transformations
+void B3dTransformationSet::SetProjection(const basegfx::B3DHomMatrix& mProject)
+{
+ maProjection = mProject;
+ PostSetProjection();
+}
+
+const basegfx::B3DHomMatrix& B3dTransformationSet::GetProjection()
+{
+ if(!mbProjectionValid)
+ CalcViewport();
+ return maProjection;
+}
+
+void B3dTransformationSet::PostSetProjection()
+{
+ // Assign and compute inverse
+ maInvProjection = GetProjection();
+ maInvProjection.invert();
+}
+
+/// Transformations for viewport
+void B3dTransformationSet::CalcViewport()
+{
+ // Parameters for projection
+ double fLeft(mfLeftBound);
+ double fRight(mfRightBound);
+ double fBottom(mfBottomBound);
+ double fTop(mfTopBound);
+
+ // Adjust projection to aspect ratio, if set
+ if(GetRatio() != 0.0)
+ {
+ // Compute current aspect ratio of boundaries
+ double fBoundWidth = static_cast<double>(maViewportRectangle.GetWidth() + 1);
+ double fBoundHeight = static_cast<double>(maViewportRectangle.GetHeight() + 1);
+ double fActRatio = 1;
+ double fFactor;
+
+ if(fBoundWidth != 0.0)
+ fActRatio = fBoundHeight / fBoundWidth;
+ // FIXME else in this case has a lot of problems, should this return.
+
+ // scale down larger part
+ if(fActRatio > mfRatio)
+ {
+ // scale down Y
+ fFactor = fActRatio;
+ fTop *= fFactor;
+ fBottom *= fFactor;
+ }
+ else
+ {
+ // scale down X
+ fFactor = 1.0 / fActRatio;
+ fRight *= fFactor;
+ fLeft *= fFactor;
+ }
+ }
+
+ // Do projection and object areas overlap?
+ maSetBound = maViewportRectangle;
+
+ // Reset projection with new values
+ basegfx::B3DHomMatrix aNewProjection;
+
+ // #i36281#
+ // OpenGL needs a little more rough additional size to not let
+ // the front face vanish. Changed from SMALL_DVALUE to 0.000001,
+ // which is 1/10000th, compared with 1/tenth of a million from SMALL_DVALUE.
+ const double fDistPart((gfFarBound - gfNearBound) * 0.0001);
+
+ // To avoid critical clipping, set Near & Far generously
+ if(mbPerspective)
+ {
+ Frustum(aNewProjection, fLeft, fRight, fBottom, fTop, gfNearBound - fDistPart, gfFarBound + fDistPart);
+ }
+ else
+ {
+ Ortho(aNewProjection, fLeft, fRight, fBottom, fTop, gfNearBound - fDistPart, gfFarBound + fDistPart);
+ }
+
+ // Set to true to guarantee loop termination
+ mbProjectionValid = true;
+
+ // set new projection
+ SetProjection(aNewProjection);
+
+ // fill parameters for ViewportTransformation
+ // Translation
+ maTranslate.setX(static_cast<double>(maSetBound.Left()) + ((maSetBound.GetWidth() - 1) / 2.0));
+ maTranslate.setY(static_cast<double>(maSetBound.Top()) + ((maSetBound.GetHeight() - 1) / 2.0));
+ maTranslate.setZ(ZBUFFER_DEPTH_RANGE / 2.0);
+
+ // Scaling
+ maScale.setX((maSetBound.GetWidth() - 1) / 2.0);
+ maScale.setY((maSetBound.GetHeight() - 1) / -2.0);
+ maScale.setZ(ZBUFFER_DEPTH_RANGE / 2.0);
+}
+
+void B3dTransformationSet::SetRatio(double fNew)
+{
+ if(mfRatio != fNew)
+ {
+ mfRatio = fNew;
+ mbProjectionValid = false;
+ }
+}
+
+void B3dTransformationSet::SetDeviceRectangle(double fL, double fR, double fB, double fT)
+{
+ if(fL != mfLeftBound || fR != mfRightBound || fB != mfBottomBound || fT != mfTopBound)
+ {
+ mfLeftBound = fL;
+ mfRightBound = fR;
+ mfBottomBound = fB;
+ mfTopBound = fT;
+
+ mbProjectionValid = false;
+
+ // Broadcast changes
+ DeviceRectangleChange();
+ }
+}
+
+void B3dTransformationSet::DeviceRectangleChange()
+{
+}
+
+void B3dTransformationSet::SetPerspective(bool bNew)
+{
+ if(mbPerspective != bNew)
+ {
+ mbPerspective = bNew;
+ mbProjectionValid = false;
+ }
+}
+
+void B3dTransformationSet::SetViewportRectangle(tools::Rectangle const & rRect, tools::Rectangle const & rVisible)
+{
+ if(rRect != maViewportRectangle || rVisible != maVisibleRectangle)
+ {
+ maViewportRectangle = rRect;
+ maVisibleRectangle = rVisible;
+
+ mbProjectionValid = false;
+ }
+}
+
+// direct access to various transformations
+
+basegfx::B3DPoint B3dTransformationSet::WorldToEyeCoor(const basegfx::B3DPoint& rVec)
+{
+ basegfx::B3DPoint aVec(rVec);
+ aVec *= maOrientation;
+ return aVec;
+}
+
+basegfx::B3DPoint B3dTransformationSet::EyeToWorldCoor(const basegfx::B3DPoint& rVec)
+{
+ basegfx::B3DPoint aVec(rVec);
+ aVec *= maInvOrientation;
+ return aVec;
+}
+
+// B3dViewport -----------------------------------------------------------------
+
+B3dViewport::B3dViewport()
+: aVRP(0, 0, 0),
+ aVPN(0, 0, 1),
+ aVUV(0, 1, 0)
+{
+ CalcOrientation();
+}
+
+B3dViewport::~B3dViewport()
+{
+}
+
+void B3dViewport::SetVUV(const basegfx::B3DVector& rNewVUV)
+{
+ aVUV = rNewVUV;
+ CalcOrientation();
+}
+
+void B3dViewport::SetViewportValues(
+ const basegfx::B3DPoint& rNewVRP,
+ const basegfx::B3DVector& rNewVPN,
+ const basegfx::B3DVector& rNewVUV)
+{
+ aVRP = rNewVRP;
+ aVPN = rNewVPN;
+ aVUV = rNewVUV;
+ CalcOrientation();
+}
+
+void B3dViewport::CalcOrientation()
+{
+ SetOrientation(aVRP, aVPN, aVUV);
+}
+
+// B3dCamera -------------------------------------------------------------------
+
+B3dCamera::B3dCamera(
+ const basegfx::B3DPoint& rPos, const basegfx::B3DVector& rLkAt,
+ double fFocLen, double fBnkAng)
+: aPosition(rPos),
+ aLookAt(rLkAt),
+ fFocalLength(fFocLen),
+ fBankAngle(fBnkAng)
+{
+ CalcNewViewportValues();
+}
+
+B3dCamera::~B3dCamera()
+{
+}
+
+void B3dCamera::DeviceRectangleChange()
+{
+ // call parent
+ B3dViewport::DeviceRectangleChange();
+
+ // react to changes
+ CalcNewViewportValues();
+}
+
+void B3dCamera::CalcNewViewportValues()
+{
+ basegfx::B3DVector aNewVPN(aPosition - aLookAt);
+
+ basegfx::B3DVector aNewVUV(0.0, 1.0, 0.0);
+ if(aNewVPN.getLength() < aNewVPN.getY())
+ aNewVUV.setX(0.5);
+
+ aNewVUV.normalize();
+ aNewVPN.normalize();
+
+ basegfx::B3DVector aNewToTheRight = aNewVPN.getPerpendicular(aNewVUV);
+ aNewToTheRight.normalize();
+ aNewVUV = aNewToTheRight.getPerpendicular(aNewVPN);
+ aNewVUV.normalize();
+
+ SetViewportValues(aPosition, aNewVPN, aNewVUV);
+ CalcFocalLength();
+
+ if(fBankAngle != 0.0)
+ {
+ basegfx::B3DHomMatrix aRotMat;
+ aRotMat.rotate(0.0, 0.0, fBankAngle);
+ basegfx::B3DVector aUp(0.0, 1.0, 0.0);
+ aUp *= aRotMat;
+ aUp = EyeToWorldCoor(aUp);
+ aUp.normalize();
+ SetVUV(aUp);
+ }
+}
+
+void B3dCamera::CalcFocalLength()
+{
+ double fWidth = GetDeviceRectangleWidth();
+
+ // Adjust focal length based on given position
+ basegfx::B3DPoint aOldPosition = WorldToEyeCoor({});
+ if(fWidth != 0.0)
+ fFocalLength = aOldPosition.getZ() / fWidth * 35.0;
+ if(fFocalLength < 5.0)
+ fFocalLength = 5.0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/bigint.cxx b/tools/source/generic/bigint.cxx
new file mode 100644
index 0000000000..51810cab17
--- /dev/null
+++ b/tools/source/generic/bigint.cxx
@@ -0,0 +1,852 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <math.h>
+
+#include <osl/diagnose.h>
+#include <tools/bigint.hxx>
+
+#include <algorithm>
+#include <string.h>
+
+/**
+ * The range in which we can perform add/sub without fear of overflow
+ */
+const sal_Int32 MY_MAXLONG = 0x3fffffff;
+const sal_Int32 MY_MINLONG = -MY_MAXLONG;
+
+/*
+ * The algorithms for Addition, Subtraction, Multiplication and Division
+ * of large numbers originate from SEMINUMERICAL ALGORITHMS by
+ * DONALD E. KNUTH in the series The Art of Computer Programming:
+ * chapter 4.3.1. The Classical Algorithms.
+ */
+
+// TODO: Needs conversion to sal_uInt16/INT16/sal_uInt32/sal_Int32
+void BigInt::MakeBigInt( const BigInt& rVal )
+{
+ if ( rVal.nLen != 0 )
+ {
+ memcpy( static_cast<void*>(this), static_cast<const void*>(&rVal), sizeof( BigInt ) );
+ while ( nLen > 1 && nNum[nLen-1] == 0 )
+ nLen--;
+ }
+ else
+ {
+ nVal = rVal.nVal;
+ sal_uInt32 nTmp;
+ if (nVal < 0)
+ {
+ bIsNeg = true;
+ nTmp = -static_cast<sal_Int64>(nVal);
+ }
+ else
+ {
+ bIsNeg = false;
+ nTmp = nVal;
+ }
+
+ nNum[0] = static_cast<sal_uInt16>(nTmp & 0xffffL);
+ nNum[1] = static_cast<sal_uInt16>(nTmp >> 16);
+ if ( nTmp & 0xffff0000L )
+ nLen = 2;
+ else
+ nLen = 1;
+ }
+}
+
+void BigInt::Normalize()
+{
+ if ( nLen != 0 )
+ {
+ while ( nLen > 1 && nNum[nLen-1] == 0 )
+ nLen--;
+
+ if ( nLen < 3 )
+ {
+ sal_Int32 newVal;
+ if ( nLen < 2 )
+ newVal = nNum[0];
+ else if ( nNum[1] & 0x8000 )
+ return;
+ else
+ newVal = (static_cast<sal_Int32>(nNum[1]) << 16) + nNum[0];
+
+ nLen = 0;
+ nVal = newVal;
+
+ if ( bIsNeg )
+ nVal = -nVal;
+ }
+ // else nVal is undefined !!! W.P.
+ }
+ // why? nVal is undefined ??? W.P.
+ else if ( nVal & 0xFFFF0000L )
+ nLen = 2;
+ else
+ nLen = 1;
+}
+
+void BigInt::Mult( const BigInt &rVal, sal_uInt16 nMul )
+{
+ sal_uInt16 nK = 0;
+ for ( int i = 0; i < rVal.nLen; i++ )
+ {
+ sal_uInt32 nTmp = static_cast<sal_uInt32>(rVal.nNum[i]) * static_cast<sal_uInt32>(nMul) + nK;
+ nK = static_cast<sal_uInt16>(nTmp >> 16);
+ nNum[i] = static_cast<sal_uInt16>(nTmp);
+ }
+
+ if ( nK )
+ {
+ nNum[rVal.nLen] = nK;
+ nLen = rVal.nLen + 1;
+ }
+ else
+ nLen = rVal.nLen;
+
+ bIsNeg = rVal.bIsNeg;
+}
+
+void BigInt::Div( sal_uInt16 nDiv, sal_uInt16& rRem )
+{
+ sal_uInt32 nK = 0;
+ for ( int i = nLen - 1; i >= 0; i-- )
+ {
+ sal_uInt32 nTmp = static_cast<sal_uInt32>(nNum[i]) + (nK << 16);
+ nNum[i] = static_cast<sal_uInt16>(nTmp / nDiv);
+ nK = nTmp % nDiv;
+ }
+ rRem = static_cast<sal_uInt16>(nK);
+
+ if ( nNum[nLen-1] == 0 )
+ nLen -= 1;
+}
+
+bool BigInt::IsLess( const BigInt& rVal ) const
+{
+ if ( rVal.nLen < nLen)
+ return true;
+ if ( rVal.nLen > nLen )
+ return false;
+
+ int i;
+ for ( i = nLen - 1; i > 0 && nNum[i] == rVal.nNum[i]; i-- )
+ {
+ }
+ return rVal.nNum[i] < nNum[i];
+}
+
+void BigInt::AddLong( BigInt& rB, BigInt& rErg )
+{
+ if ( bIsNeg == rB.bIsNeg )
+ {
+ int i;
+ char len;
+
+ // if length of the two values differ, fill remaining positions
+ // of the smaller value with zeros.
+ if (nLen >= rB.nLen)
+ {
+ len = nLen;
+ for (i = rB.nLen; i < len; i++)
+ rB.nNum[i] = 0;
+ }
+ else
+ {
+ len = rB.nLen;
+ for (i = nLen; i < len; i++)
+ nNum[i] = 0;
+ }
+
+ // Add numerals, starting from the back
+ sal_Int32 k;
+ sal_Int32 nZ = 0;
+ for (i = 0, k = 0; i < len; i++) {
+ nZ = static_cast<sal_Int32>(nNum[i]) + static_cast<sal_Int32>(rB.nNum[i]) + k;
+ if (nZ & 0xff0000L)
+ k = 1;
+ else
+ k = 0;
+ rErg.nNum[i] = static_cast<sal_uInt16>(nZ & 0xffffL);
+ }
+ // If an overflow occurred, add to solution
+ if (nZ & 0xff0000L) // or if(k)
+ {
+ rErg.nNum[i] = 1;
+ len++;
+ }
+ // Set length and sign
+ rErg.nLen = len;
+ rErg.bIsNeg = bIsNeg && rB.bIsNeg;
+ }
+ // If one of the values is negative, perform subtraction instead
+ else if (bIsNeg)
+ {
+ bIsNeg = false;
+ rB.SubLong(*this, rErg);
+ bIsNeg = true;
+ }
+ else
+ {
+ rB.bIsNeg = false;
+ SubLong(rB, rErg);
+ rB.bIsNeg = true;
+ }
+}
+
+void BigInt::SubLong( BigInt& rB, BigInt& rErg )
+{
+ if ( bIsNeg == rB.bIsNeg )
+ {
+ int i;
+ char len;
+ sal_Int32 nZ, k;
+
+ // if length of the two values differ, fill remaining positions
+ // of the smaller value with zeros.
+ if (nLen >= rB.nLen)
+ {
+ len = nLen;
+ for (i = rB.nLen; i < len; i++)
+ rB.nNum[i] = 0;
+ }
+ else
+ {
+ len = rB.nLen;
+ for (i = nLen; i < len; i++)
+ nNum[i] = 0;
+ }
+
+ if ( IsLess(rB) )
+ {
+ for (i = 0, k = 0; i < len; i++)
+ {
+ nZ = static_cast<sal_Int32>(nNum[i]) - static_cast<sal_Int32>(rB.nNum[i]) + k;
+ if (nZ < 0)
+ k = -1;
+ else
+ k = 0;
+ rErg.nNum[i] = static_cast<sal_uInt16>(nZ & 0xffffL);
+ }
+ rErg.bIsNeg = bIsNeg;
+ }
+ else
+ {
+ for (i = 0, k = 0; i < len; i++)
+ {
+ nZ = static_cast<sal_Int32>(rB.nNum[i]) - static_cast<sal_Int32>(nNum[i]) + k;
+ if (nZ < 0)
+ k = -1;
+ else
+ k = 0;
+ rErg.nNum[i] = static_cast<sal_uInt16>(nZ & 0xffffL);
+ }
+ // if a < b, revert sign
+ rErg.bIsNeg = !bIsNeg;
+ }
+ rErg.nLen = len;
+ }
+ // If one of the values is negative, perform addition instead
+ else if (bIsNeg)
+ {
+ bIsNeg = false;
+ AddLong(rB, rErg);
+ bIsNeg = true;
+ rErg.bIsNeg = true;
+ }
+ else
+ {
+ rB.bIsNeg = false;
+ AddLong(rB, rErg);
+ rB.bIsNeg = true;
+ rErg.bIsNeg = false;
+ }
+}
+
+void BigInt::MultLong( const BigInt& rB, BigInt& rErg ) const
+{
+ int i, j;
+ sal_uInt32 nZ, k;
+
+ rErg.bIsNeg = bIsNeg != rB.bIsNeg;
+ rErg.nLen = nLen + rB.nLen;
+
+ for (i = 0; i < rErg.nLen; i++)
+ rErg.nNum[i] = 0;
+
+ for (j = 0; j < rB.nLen; j++)
+ {
+ for (i = 0, k = 0; i < nLen; i++)
+ {
+ nZ = static_cast<sal_uInt32>(nNum[i]) * static_cast<sal_uInt32>(rB.nNum[j]) +
+ static_cast<sal_uInt32>(rErg.nNum[i + j]) + k;
+ rErg.nNum[i + j] = static_cast<sal_uInt16>(nZ & 0xffffU);
+ k = nZ >> 16;
+ }
+ rErg.nNum[i + j] = static_cast<sal_uInt16>(k);
+ }
+}
+
+void BigInt::DivLong( const BigInt& rB, BigInt& rErg ) const
+{
+ int i, j;
+ sal_uInt16 nK, nQ, nMult;
+ sal_uInt16 nLenB = rB.nLen;
+ sal_uInt16 nLenB1 = rB.nLen - 1;
+ BigInt aTmpA, aTmpB;
+
+ nMult = static_cast<sal_uInt16>(0x10000L / (static_cast<sal_Int32>(rB.nNum[nLenB1]) + 1));
+
+ aTmpA.Mult( *this, nMult );
+ if ( aTmpA.nLen == nLen )
+ {
+ aTmpA.nNum[aTmpA.nLen] = 0;
+ aTmpA.nLen++;
+ }
+
+ aTmpB.Mult( rB, nMult );
+
+ for (j = aTmpA.nLen - 1; j >= nLenB; j--)
+ { // guess divisor
+ sal_uInt32 nTmp = ( static_cast<sal_uInt32>(aTmpA.nNum[j]) << 16 ) + aTmpA.nNum[j - 1];
+ if (aTmpA.nNum[j] == aTmpB.nNum[nLenB1])
+ nQ = 0xFFFF;
+ else
+ nQ = static_cast<sal_uInt16>(nTmp / aTmpB.nNum[nLenB1]);
+
+ if ( (static_cast<sal_uInt32>(aTmpB.nNum[nLenB1 - 1]) * nQ) >
+ ((nTmp - static_cast<sal_uInt32>(aTmpB.nNum[nLenB1]) * nQ) << 16) + aTmpA.nNum[j - 2])
+ nQ--;
+ // Start division
+ nK = 0;
+ for (i = 0; i < nLenB; i++)
+ {
+ nTmp = static_cast<sal_uInt32>(aTmpA.nNum[j - nLenB + i])
+ - (static_cast<sal_uInt32>(aTmpB.nNum[i]) * nQ)
+ - nK;
+ aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp);
+ nK = static_cast<sal_uInt16>(nTmp >> 16);
+ if ( nK )
+ nK = static_cast<sal_uInt16>(0x10000U - nK);
+ }
+ sal_uInt16& rNum( aTmpA.nNum[j - nLenB + i] );
+ rNum -= nK;
+ if (aTmpA.nNum[j - nLenB + i] == 0)
+ rErg.nNum[j - nLenB] = nQ;
+ else
+ {
+ rErg.nNum[j - nLenB] = nQ - 1;
+ nK = 0;
+ for (i = 0; i < nLenB; i++)
+ {
+ nTmp = aTmpA.nNum[j - nLenB + i] + aTmpB.nNum[i] + nK;
+ aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp & 0xFFFFL);
+ if (nTmp & 0xFFFF0000L)
+ nK = 1;
+ else
+ nK = 0;
+ }
+ }
+ }
+
+ rErg.bIsNeg = bIsNeg != rB.bIsNeg;
+ rErg.nLen = nLen - rB.nLen + 1;
+}
+
+void BigInt::ModLong( const BigInt& rB, BigInt& rErg ) const
+{
+ sal_uInt16 i, j;
+ sal_uInt16 nK, nQ, nMult;
+ sal_Int16 nLenB = rB.nLen;
+ sal_Int16 nLenB1 = rB.nLen - 1;
+ BigInt aTmpA, aTmpB;
+
+ nMult = static_cast<sal_uInt16>(0x10000L / (static_cast<sal_Int32>(rB.nNum[nLenB1]) + 1));
+
+ aTmpA.Mult( *this, nMult);
+ if ( aTmpA.nLen == nLen )
+ {
+ aTmpA.nNum[aTmpA.nLen] = 0;
+ aTmpA.nLen++;
+ }
+
+ aTmpB.Mult( rB, nMult);
+
+ for (j = aTmpA.nLen - 1; j >= nLenB; j--)
+ { // Guess divisor
+ sal_uInt32 nTmp = ( static_cast<sal_uInt32>(aTmpA.nNum[j]) << 16 ) + aTmpA.nNum[j - 1];
+ if (aTmpA.nNum[j] == aTmpB.nNum[nLenB1])
+ nQ = 0xFFFF;
+ else
+ nQ = static_cast<sal_uInt16>(nTmp / aTmpB.nNum[nLenB1]);
+
+ if ( (static_cast<sal_uInt32>(aTmpB.nNum[nLenB1 - 1]) * nQ) >
+ ((nTmp - aTmpB.nNum[nLenB1] * nQ) << 16) + aTmpA.nNum[j - 2])
+ nQ--;
+ // Start division
+ nK = 0;
+ for (i = 0; i < nLenB; i++)
+ {
+ nTmp = static_cast<sal_uInt32>(aTmpA.nNum[j - nLenB + i])
+ - (static_cast<sal_uInt32>(aTmpB.nNum[i]) * nQ)
+ - nK;
+ aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp);
+ nK = static_cast<sal_uInt16>(nTmp >> 16);
+ if ( nK )
+ nK = static_cast<sal_uInt16>(0x10000U - nK);
+ }
+ sal_uInt16& rNum( aTmpA.nNum[j - nLenB + i] );
+ rNum = rNum - nK;
+ if (aTmpA.nNum[j - nLenB + i] == 0)
+ rErg.nNum[j - nLenB] = nQ;
+ else
+ {
+ rErg.nNum[j - nLenB] = nQ - 1;
+ nK = 0;
+ for (i = 0; i < nLenB; i++) {
+ nTmp = aTmpA.nNum[j - nLenB + i] + aTmpB.nNum[i] + nK;
+ aTmpA.nNum[j - nLenB + i] = static_cast<sal_uInt16>(nTmp & 0xFFFFL);
+ if (nTmp & 0xFFFF0000L)
+ nK = 1;
+ else
+ nK = 0;
+ }
+ }
+ }
+
+ rErg = aTmpA;
+ rErg.Div( nMult, nQ );
+}
+
+bool BigInt::ABS_IsLess( const BigInt& rB ) const
+{
+ if (nLen != 0 || rB.nLen != 0)
+ {
+ BigInt nA, nB;
+ nA.MakeBigInt( *this );
+ nB.MakeBigInt( rB );
+ if (nA.nLen == nB.nLen)
+ {
+ int i;
+ for (i = nA.nLen - 1; i > 0 && nA.nNum[i] == nB.nNum[i]; i--)
+ {
+ }
+ return nA.nNum[i] < nB.nNum[i];
+ }
+ else
+ return nA.nLen < nB.nLen;
+ }
+ if ( nVal < 0 )
+ if ( rB.nVal < 0 )
+ return nVal > rB.nVal;
+ else
+ return nVal > -rB.nVal;
+ else
+ if ( rB.nVal < 0 )
+ return nVal < -rB.nVal;
+ else
+ return nVal < rB.nVal;
+}
+
+BigInt::BigInt( const BigInt& rBigInt )
+ : nLen(0)
+ , bIsNeg(false)
+{
+ if ( rBigInt.nLen != 0 )
+ memcpy( static_cast<void*>(this), static_cast<const void*>(&rBigInt), sizeof( BigInt ) );
+ else
+ nVal = rBigInt.nVal;
+}
+
+BigInt::BigInt( std::u16string_view rString )
+ : nLen(0)
+{
+ bIsNeg = false;
+ nVal = 0;
+
+ bool bNeg = false;
+ auto p = rString.begin();
+ auto pEnd = rString.end();
+ if (p == pEnd)
+ return;
+ if ( *p == '-' )
+ {
+ bNeg = true;
+ p++;
+ }
+ if (p == pEnd)
+ return;
+ while( p != pEnd && *p >= '0' && *p <= '9' )
+ {
+ *this *= 10;
+ *this += *p - '0';
+ p++;
+ }
+ if ( nLen != 0 )
+ bIsNeg = bNeg;
+ else if( bNeg )
+ nVal = -nVal;
+}
+
+BigInt::BigInt( double nValue )
+ : nVal(0)
+{
+ if ( nValue < 0 )
+ {
+ nValue *= -1;
+ bIsNeg = true;
+ }
+ else
+ {
+ bIsNeg = false;
+ }
+
+ if ( nValue < 1 )
+ {
+ nVal = 0;
+ nLen = 0;
+ }
+ else
+ {
+ int i=0;
+
+ while ( ( nValue > 65536.0 ) && ( i < MAX_DIGITS ) )
+ {
+ nNum[i] = static_cast<sal_uInt16>(fmod( nValue, 65536.0 ));
+ nValue -= nNum[i];
+ nValue /= 65536.0;
+ i++;
+ }
+ if ( i < MAX_DIGITS )
+ nNum[i++] = static_cast<sal_uInt16>(nValue);
+
+ nLen = i;
+
+ if ( i < 3 )
+ Normalize();
+ }
+}
+
+BigInt::BigInt( sal_uInt32 nValue )
+ : nVal(0)
+{
+ if ( nValue & 0x80000000U )
+ {
+ bIsNeg = false;
+ nNum[0] = static_cast<sal_uInt16>(nValue & 0xffffU);
+ nNum[1] = static_cast<sal_uInt16>(nValue >> 16);
+ nLen = 2;
+ }
+ else
+ {
+ bIsNeg = false;
+ nVal = nValue;
+ nLen = 0;
+ }
+}
+
+BigInt::BigInt( sal_Int64 nValue )
+ : nVal(0)
+{
+ bIsNeg = nValue < 0;
+ nLen = 0;
+
+ if ((nValue >= SAL_MIN_INT32) && (nValue <= SAL_MAX_INT32))
+ {
+ nVal = static_cast<sal_Int32>(nValue);
+ }
+ else
+ {
+ sal_uInt64 nUValue = static_cast<sal_uInt64>(bIsNeg ? -nValue : nValue);
+ for (int i = 0; (i != sizeof(sal_uInt64) / 2) && (nUValue != 0); ++i)
+ {
+ nNum[i] = static_cast<sal_uInt16>(nUValue & 0xffffUL);
+ nUValue = nUValue >> 16;
+ ++nLen;
+ }
+ }
+}
+
+BigInt::operator double() const
+{
+ if ( nLen == 0 )
+ return static_cast<double>(nVal);
+ else
+ {
+ int i = nLen-1;
+ double nRet = static_cast<double>(static_cast<sal_uInt32>(nNum[i]));
+
+ while ( i )
+ {
+ nRet *= 65536.0;
+ i--;
+ nRet += static_cast<double>(static_cast<sal_uInt32>(nNum[i]));
+ }
+
+ if ( bIsNeg )
+ nRet *= -1;
+
+ return nRet;
+ }
+}
+
+BigInt& BigInt::operator=( const BigInt& rBigInt )
+{
+ if (this == &rBigInt)
+ return *this;
+
+ if ( rBigInt.nLen != 0 )
+ memcpy( static_cast<void*>(this), static_cast<const void*>(&rBigInt), sizeof( BigInt ) );
+ else
+ {
+ nLen = 0;
+ nVal = rBigInt.nVal;
+ }
+ return *this;
+}
+
+BigInt& BigInt::operator+=( const BigInt& rVal )
+{
+ if ( nLen == 0 && rVal.nLen == 0 )
+ {
+ if( nVal <= MY_MAXLONG && rVal.nVal <= MY_MAXLONG
+ && nVal >= MY_MINLONG && rVal.nVal >= MY_MINLONG )
+ { // No overflows may occur here
+ nVal += rVal.nVal;
+ return *this;
+ }
+
+ if( (nVal < 0) != (rVal.nVal < 0) )
+ { // No overflows may occur here
+ nVal += rVal.nVal;
+ return *this;
+ }
+ }
+
+ BigInt aTmp1, aTmp2;
+ aTmp1.MakeBigInt( *this );
+ aTmp2.MakeBigInt( rVal );
+ aTmp1.AddLong( aTmp2, *this );
+ Normalize();
+ return *this;
+}
+
+BigInt& BigInt::operator-=( const BigInt& rVal )
+{
+ if ( nLen == 0 && rVal.nLen == 0 )
+ {
+ if ( nVal <= MY_MAXLONG && rVal.nVal <= MY_MAXLONG &&
+ nVal >= MY_MINLONG && rVal.nVal >= MY_MINLONG )
+ { // No overflows may occur here
+ nVal -= rVal.nVal;
+ return *this;
+ }
+
+ if ( (nVal < 0) == (rVal.nVal < 0) )
+ { // No overflows may occur here
+ nVal -= rVal.nVal;
+ return *this;
+ }
+ }
+
+ BigInt aTmp1, aTmp2;
+ aTmp1.MakeBigInt( *this );
+ aTmp2.MakeBigInt( rVal );
+ aTmp1.SubLong( aTmp2, *this );
+ Normalize();
+ return *this;
+}
+
+BigInt& BigInt::operator*=( const BigInt& rVal )
+{
+ static const sal_Int32 MY_MAXSHORT = 0x00007fff;
+ static const sal_Int32 MY_MINSHORT = -MY_MAXSHORT;
+
+ if ( nLen == 0 && rVal.nLen == 0
+ && nVal <= MY_MAXSHORT && rVal.nVal <= MY_MAXSHORT
+ && nVal >= MY_MINSHORT && rVal.nVal >= MY_MINSHORT )
+ // TODO: not optimal !!! W.P.
+ { // No overflows may occur here
+ nVal *= rVal.nVal;
+ }
+ else
+ {
+ BigInt aTmp1, aTmp2;
+ aTmp1.MakeBigInt( rVal );
+ aTmp2.MakeBigInt( *this );
+ aTmp1.MultLong(aTmp2, *this);
+ Normalize();
+ }
+ return *this;
+}
+
+BigInt& BigInt::operator/=( const BigInt& rVal )
+{
+ if ( rVal.nLen == 0 )
+ {
+ if ( rVal.nVal == 0 )
+ {
+ OSL_FAIL( "BigInt::operator/ --> divide by zero" );
+ return *this;
+ }
+
+ if ( nLen == 0 )
+ {
+ // No overflows may occur here
+ nVal /= rVal.nVal;
+ return *this;
+ }
+
+ if ( rVal.nVal == 1 )
+ return *this;
+
+ if ( rVal.nVal == -1 )
+ {
+ bIsNeg = !bIsNeg;
+ return *this;
+ }
+
+ if ( rVal.nVal <= 0xFFFF && rVal.nVal >= -0xFFFF )
+ {
+ // Divide BigInt with an sal_uInt16
+ sal_uInt16 nTmp;
+ if ( rVal.nVal < 0 )
+ {
+ nTmp = static_cast<sal_uInt16>(-rVal.nVal);
+ bIsNeg = !bIsNeg;
+ }
+ else
+ nTmp = static_cast<sal_uInt16>(rVal.nVal);
+
+ Div( nTmp, nTmp );
+ Normalize();
+ return *this;
+ }
+ }
+
+ if ( ABS_IsLess( rVal ) )
+ {
+ *this = BigInt( 0 );
+ return *this;
+ }
+
+ // Divide BigInt with BigInt
+ BigInt aTmp1, aTmp2;
+ aTmp1.MakeBigInt( *this );
+ aTmp2.MakeBigInt( rVal );
+ aTmp1.DivLong(aTmp2, *this);
+ Normalize();
+ return *this;
+}
+
+BigInt& BigInt::operator%=( const BigInt& rVal )
+{
+ if ( rVal.nLen == 0 )
+ {
+ if ( rVal.nVal == 0 )
+ {
+ OSL_FAIL( "BigInt::operator/ --> divide by zero" );
+ return *this;
+ }
+
+ if ( nLen == 0 )
+ {
+ // No overflows may occur here
+ nVal %= rVal.nVal;
+ return *this;
+ }
+
+ if ( rVal.nVal <= 0xFFFF && rVal.nVal >= -0xFFFF )
+ {
+ // Divide Bigint by int16
+ sal_uInt16 nTmp;
+ if ( rVal.nVal < 0 )
+ {
+ nTmp = static_cast<sal_uInt16>(-rVal.nVal);
+ bIsNeg = !bIsNeg;
+ }
+ else
+ nTmp = static_cast<sal_uInt16>(rVal.nVal);
+
+ Div( nTmp, nTmp );
+ *this = BigInt( nTmp );
+ return *this;
+ }
+ }
+
+ if ( ABS_IsLess( rVal ) )
+ return *this;
+
+ // Divide BigInt with BigInt
+ BigInt aTmp1, aTmp2;
+ aTmp1.MakeBigInt( *this );
+ aTmp2.MakeBigInt( rVal );
+ aTmp1.ModLong(aTmp2, *this);
+ Normalize();
+ return *this;
+}
+
+bool operator==( const BigInt& rVal1, const BigInt& rVal2 )
+{
+ if (rVal1.nLen == 0 && rVal2.nLen == 0)
+ return rVal1.nVal == rVal2.nVal;
+
+ BigInt nA, nB;
+ nA.MakeBigInt(rVal1);
+ nB.MakeBigInt(rVal2);
+ return nA.bIsNeg == nB.bIsNeg && nA.nLen == nB.nLen
+ && std::equal(nA.nNum, nA.nNum + nA.nLen, nB.nNum);
+}
+
+bool operator<( const BigInt& rVal1, const BigInt& rVal2 )
+{
+ if (rVal1.nLen == 0 && rVal2.nLen == 0)
+ return rVal1.nVal < rVal2.nVal;
+
+ BigInt nA, nB;
+ nA.MakeBigInt(rVal1);
+ nB.MakeBigInt(rVal2);
+ if (nA.bIsNeg != nB.bIsNeg)
+ return !nB.bIsNeg;
+ if (nA.nLen != nB.nLen)
+ return nA.bIsNeg ? (nA.nLen > nB.nLen) : (nA.nLen < nB.nLen);
+ int i = nA.nLen - 1;
+ while (i > 0 && nA.nNum[i] == nB.nNum[i])
+ --i;
+ return nA.bIsNeg ? (nA.nNum[i] > nB.nNum[i]) : (nA.nNum[i] < nB.nNum[i]);
+}
+
+tools::Long BigInt::Scale( tools::Long nVal, tools::Long nMul, tools::Long nDiv )
+{
+ BigInt aVal( nVal );
+
+ aVal *= nMul;
+
+ if ( aVal.IsNeg() != ( nDiv < 0 ) )
+ aVal -= nDiv / 2; // for correct rounding
+ else
+ aVal += nDiv / 2; // for correct rounding
+
+ aVal /= nDiv;
+
+ return tools::Long( aVal );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/color.cxx b/tools/source/generic/color.cxx
new file mode 100644
index 0000000000..421045b588
--- /dev/null
+++ b/tools/source/generic/color.cxx
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <algorithm>
+#include <iomanip>
+#include <sstream>
+#include <stdlib.h>
+
+#include <tools/color.hxx>
+#include <tools/helpers.hxx>
+#include <tools/long.hxx>
+#include <o3tl/string_view.hxx>
+#include <basegfx/color/bcolortools.hxx>
+
+void Color::IncreaseLuminance(sal_uInt8 cLumInc)
+{
+ R = sal_uInt8(std::clamp(R + cLumInc, 0, 255));
+ G = sal_uInt8(std::clamp(G + cLumInc, 0, 255));
+ B = sal_uInt8(std::clamp(B + cLumInc, 0, 255));
+}
+
+void Color::DecreaseLuminance(sal_uInt8 cLumDec)
+{
+ R = sal_uInt8(std::clamp(R - cLumDec, 0, 255));
+ G = sal_uInt8(std::clamp(G - cLumDec, 0, 255));
+ B = sal_uInt8(std::clamp(B - cLumDec, 0, 255));
+}
+
+void Color::DecreaseContrast(sal_uInt8 nContDec)
+{
+ if (nContDec)
+ {
+ const double fM = (128.0 - 0.4985 * nContDec) / 128.0;
+ const double fOff = 128.0 - fM * 128.0;
+
+ R = sal_uInt8(std::clamp(FRound(R * fM + fOff), tools::Long(0), tools::Long(255)));
+ G = sal_uInt8(std::clamp(FRound(G * fM + fOff), tools::Long(0), tools::Long(255)));
+ B = sal_uInt8(std::clamp(FRound(B * fM + fOff), tools::Long(0), tools::Long(255)));
+ }
+}
+
+// color space conversion
+
+void Color::RGBtoHSB( sal_uInt16& nHue, sal_uInt16& nSat, sal_uInt16& nBri ) const
+{
+ sal_uInt8 c[3];
+ sal_uInt8 cMax, cMin;
+
+ c[0] = R;
+ c[1] = G;
+ c[2] = B;
+
+ cMax = c[0];
+ if( c[1] > cMax )
+ cMax = c[1];
+ if( c[2] > cMax )
+ cMax = c[2];
+
+ // Brightness = max(R, G, B);
+ nBri = cMax * 100 / 255;
+
+ cMin = c[0];
+ if( c[1] < cMin )
+ cMin = c[1];
+ if( c[2] < cMin )
+ cMin = c[2];
+
+ sal_uInt8 cDelta = cMax - cMin;
+
+ // Saturation = max - min / max
+ if( nBri > 0 )
+ nSat = cDelta * 100 / cMax;
+ else
+ nSat = 0;
+
+ if( nSat == 0 )
+ nHue = 0; // Default = undefined
+ else
+ {
+ double dHue = 0.0;
+
+ if( c[0] == cMax )
+ {
+ dHue = static_cast<double>( c[1] - c[2] ) / static_cast<double>(cDelta);
+ }
+ else if( c[1] == cMax )
+ {
+ dHue = 2.0 + static_cast<double>( c[2] - c[0] ) / static_cast<double>(cDelta);
+ }
+ else if ( c[2] == cMax )
+ {
+ dHue = 4.0 + static_cast<double>( c[0] - c[1] ) / static_cast<double>(cDelta);
+ }
+ dHue *= 60.0;
+
+ if( dHue < 0.0 )
+ dHue += 360.0;
+
+ nHue = static_cast<sal_uInt16>(dHue);
+ }
+}
+
+Color Color::HSBtoRGB( sal_uInt16 nHue, sal_uInt16 nSat, sal_uInt16 nBri )
+{
+ sal_uInt8 cR=0,cG=0,cB=0;
+ sal_uInt8 nB = static_cast<sal_uInt8>( nBri * 255 / 100 );
+
+ if( nSat == 0 )
+ {
+ cR = nB;
+ cG = nB;
+ cB = nB;
+ }
+ else
+ {
+ double dH = nHue;
+ double f;
+ sal_uInt16 n;
+ if( dH == 360.0 )
+ dH = 0.0;
+
+ dH /= 60.0;
+ n = static_cast<sal_uInt16>(dH);
+ f = dH - n;
+
+ sal_uInt8 a = static_cast<sal_uInt8>( nB * ( 100 - nSat ) / 100 );
+ sal_uInt8 b = static_cast<sal_uInt8>( nB * ( 100 - ( static_cast<double>(nSat) * f ) ) / 100 );
+ sal_uInt8 c = static_cast<sal_uInt8>( nB * ( 100 - ( static_cast<double>(nSat) * ( 1.0 - f ) ) ) / 100 );
+
+ switch( n )
+ {
+ case 0: cR = nB; cG = c; cB = a; break;
+ case 1: cR = b; cG = nB; cB = a; break;
+ case 2: cR = a; cG = nB; cB = c; break;
+ case 3: cR = a; cG = b; cB = nB; break;
+ case 4: cR = c; cG = a; cB = nB; break;
+ case 5: cR = nB; cG = a; cB = b; break;
+ }
+ }
+
+ return Color( cR, cG, cB );
+}
+
+Color Color::STRtoRGB(std::u16string_view colorname)
+{
+ Color col;
+ if(colorname.empty()) return col;
+
+ switch(colorname.size()){
+ case 7:
+ col.mValue = o3tl::toUInt32(colorname.substr(1,6), 16);
+ break;
+ case 6:
+ col.mValue = o3tl::toUInt32(colorname, 16);
+ break;
+ case 4:
+ {
+ sal_Unicode data[6] = { colorname[1], colorname[1], colorname[2],
+ colorname[2], colorname[3], colorname[3] };
+ col.mValue = o3tl::toUInt32(std::u16string_view(data,6), 16);
+ break;
+ }
+ case 3:
+ {
+ sal_Unicode data[6] = { colorname[0], colorname[0], colorname[1],
+ colorname[1], colorname[2], colorname[2] };
+ col.mValue = o3tl::toUInt32(std::u16string_view(data,6), 16);
+ break;
+ }
+ default:
+ break;
+ }
+ return col;
+}
+
+OUString Color::AsRGBHexString() const
+{
+ std::stringstream ss;
+ ss << std::hex << std::setfill ('0') << std::setw(6) << sal_uInt32(GetRGBColor());
+ return OUString::createFromAscii(ss.str());
+}
+
+OUString Color::AsRGBHEXString() const
+{
+ std::stringstream ss;
+ ss << std::hex << std::uppercase << std::setfill ('0') << std::setw(6) << sal_uInt32(GetRGBColor());
+ return OUString::createFromAscii(ss.str());
+}
+
+void Color::ApplyTintOrShade(sal_Int16 n100thPercent)
+{
+ if (n100thPercent == 0)
+ return;
+
+ basegfx::BColor aBColor = basegfx::utils::rgb2hsl(getBColor());
+ double fFactor = 1.0 - (std::abs(double(n100thPercent)) / 10000.0);
+ double fResult;
+
+ if (n100thPercent > 0) // tint
+ {
+ fResult = aBColor.getBlue() * fFactor + (1.0 - fFactor);
+ }
+ else // shade
+ {
+ fResult = aBColor.getBlue() * fFactor;
+ }
+
+ aBColor.setBlue(fResult);
+ aBColor = basegfx::utils::hsl2rgb(aBColor);
+
+ R = sal_uInt8(std::lround(aBColor.getRed() * 255.0));
+ G = sal_uInt8(std::lround(aBColor.getGreen() * 255.0));
+ B = sal_uInt8(std::lround(aBColor.getBlue() * 255.0));
+}
+
+void Color::ApplyLumModOff(sal_Int16 nMod, sal_Int16 nOff)
+{
+ if (nMod == 10000 && nOff == 0)
+ {
+ return;
+ }
+ // Switch to HSL, where applying these transforms is easier.
+ basegfx::BColor aBColor = basegfx::utils::rgb2hsl(getBColor());
+
+ // 50% is half luminance, 200% is double luminance. Unit is 100th percent.
+ aBColor.setBlue(std::clamp(aBColor.getBlue() * nMod / 10000, 0.0, 1.0));
+ // If color changes to black or white, it will stay gray if luminance changes again.
+ if ((aBColor.getBlue() == 0.0) || (aBColor.getBlue() == 1.0))
+ {
+ aBColor.setGreen(0.0);
+ }
+
+ // Luminance offset means hue and saturation is left unchanged. Unit is 100th percent.
+ aBColor.setBlue(std::clamp(aBColor.getBlue() + static_cast<double>(nOff) / 10000, 0.0, 1.0));
+ // If color changes to black or white, it will stay gray if luminance changes again.
+ if ((aBColor.getBlue() == 0.0) || (aBColor.getBlue() == 1.0))
+ {
+ aBColor.setGreen(0.0);
+ }
+
+ // Switch back to RGB.
+ aBColor = basegfx::utils::hsl2rgb(aBColor);
+ R = sal_uInt8(std::lround(aBColor.getRed() * 255.0));
+ G = sal_uInt8(std::lround(aBColor.getGreen() * 255.0));
+ B = sal_uInt8(std::lround(aBColor.getBlue() * 255.0));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/config.cxx b/tools/source/generic/config.cxx
new file mode 100644
index 0000000000..cc5cce9c3b
--- /dev/null
+++ b/tools/source/generic/config.cxx
@@ -0,0 +1,944 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <cstddef>
+#include <cstdlib>
+#include <string.h>
+
+#ifdef _WIN32
+#include <stdlib.h>
+#endif
+
+#include <osl/file.hxx>
+#include <tools/config.hxx>
+#include <sal/log.hxx>
+
+namespace {
+
+struct ImplKeyData
+{
+ ImplKeyData* mpNext;
+ OString maKey;
+ OString maValue;
+ bool mbIsComment;
+};
+
+}
+
+struct ImplGroupData
+{
+ ImplGroupData* mpNext;
+ ImplKeyData* mpFirstKey;
+ OString maGroupName;
+ sal_uInt16 mnEmptyLines;
+};
+
+struct ImplConfigData
+{
+ ImplGroupData* mpFirstGroup;
+ OUString maFileName;
+ sal_uInt32 mnDataUpdateId;
+ sal_uInt32 mnTimeStamp;
+ bool mbModified;
+ bool mbRead;
+ bool mbIsUTF8BOM;
+};
+
+static OUString toUncPath( const OUString& rPath )
+{
+ OUString aFileURL;
+
+ // check if rFileName is already a URL; if not make it so
+ if( rPath.startsWith( "file://"))
+ {
+ aFileURL = rPath;
+ }
+ else if( ::osl::FileBase::getFileURLFromSystemPath( rPath, aFileURL ) != ::osl::FileBase::E_None )
+ {
+ aFileURL = rPath;
+ }
+ return aFileURL;
+}
+
+static sal_uInt32 ImplSysGetConfigTimeStamp( const OUString& rFileName )
+{
+ sal_uInt32 nTimeStamp = 0;
+ ::osl::DirectoryItem aItem;
+ ::osl::FileStatus aStatus( osl_FileStatus_Mask_ModifyTime );
+
+ if( ::osl::DirectoryItem::get( rFileName, aItem ) == ::osl::FileBase::E_None &&
+ aItem.getFileStatus( aStatus ) == ::osl::FileBase::E_None )
+ {
+ nTimeStamp = aStatus.getModifyTime().Seconds;
+ }
+
+ return nTimeStamp;
+}
+
+static std::unique_ptr<sal_uInt8[]> ImplSysReadConfig( const OUString& rFileName,
+ sal_uInt64& rRead, bool& rbRead, bool& rbIsUTF8BOM, sal_uInt32& rTimeStamp )
+{
+ std::unique_ptr<sal_uInt8[]> pBuf;
+ ::osl::File aFile( rFileName );
+
+ if( aFile.open( osl_File_OpenFlag_Read ) == ::osl::FileBase::E_None )
+ {
+ sal_uInt64 nPos = 0;
+ if( aFile.getSize( nPos ) == ::osl::FileBase::E_None )
+ {
+ if (nPos > SAL_MAX_SIZE) {
+ aFile.close();
+ return nullptr;
+ }
+ pBuf.reset(new sal_uInt8[static_cast< std::size_t >(nPos)]);
+ sal_uInt64 nRead = 0;
+ if( aFile.read( pBuf.get(), nPos, nRead ) == ::osl::FileBase::E_None && nRead == nPos )
+ {
+ //skip the byte-order-mark 0xEF 0xBB 0xBF, if it was UTF8 files
+ unsigned char const BOM[3] = {0xEF, 0xBB, 0xBF};
+ if (nRead > 2 && memcmp(pBuf.get(), BOM, 3) == 0)
+ {
+ nRead -= 3;
+ memmove(pBuf.get(), pBuf.get() + 3, sal::static_int_cast<std::size_t>(nRead * sizeof(sal_uInt8)) );
+ rbIsUTF8BOM = true;
+ }
+
+ rTimeStamp = ImplSysGetConfigTimeStamp( rFileName );
+ rbRead = true;
+ rRead = nRead;
+ }
+ else
+ {
+ pBuf.reset();
+ }
+ }
+ aFile.close();
+ }
+
+ return pBuf;
+}
+
+static bool ImplSysWriteConfig( const OUString& rFileName,
+ const sal_uInt8* pBuf, sal_uInt32 nBufLen, bool rbIsUTF8BOM, sal_uInt32& rTimeStamp )
+{
+ bool bSuccess = false;
+ bool bUTF8BOMSuccess = false;
+
+ ::osl::File aFile( rFileName );
+ ::osl::FileBase::RC eError = aFile.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );
+ if( eError != ::osl::FileBase::E_None )
+ eError = aFile.open( osl_File_OpenFlag_Write );
+ if( eError == ::osl::FileBase::E_None )
+ {
+ // truncate
+ aFile.setSize( 0 );
+ sal_uInt64 nWritten;
+
+ //write the byte-order-mark 0xEF 0xBB 0xBF first , if it was UTF8 files
+ if ( rbIsUTF8BOM )
+ {
+ unsigned char const BOM[3] = {0xEF, 0xBB, 0xBF};
+ sal_uInt64 nUTF8BOMWritten;
+ if( aFile.write( BOM, 3, nUTF8BOMWritten ) == ::osl::FileBase::E_None && 3 == nUTF8BOMWritten )
+ {
+ bUTF8BOMSuccess = true;
+ }
+ }
+
+ if( aFile.write( pBuf, nBufLen, nWritten ) == ::osl::FileBase::E_None && nWritten == nBufLen )
+ {
+ bSuccess = true;
+ }
+ if ( rbIsUTF8BOM ? bSuccess && bUTF8BOMSuccess : bSuccess )
+ {
+ rTimeStamp = ImplSysGetConfigTimeStamp( rFileName );
+ }
+ }
+
+ return rbIsUTF8BOM ? bSuccess && bUTF8BOMSuccess : bSuccess;
+}
+
+namespace {
+OString makeOString(const sal_uInt8* p, sal_uInt64 n)
+{
+ if (n > SAL_MAX_INT32)
+ {
+ #ifdef _WIN32
+ abort();
+ #else
+ ::std::abort(); //TODO: handle this gracefully
+ #endif
+ }
+ return OString(
+ reinterpret_cast< char const * >(p),
+ sal::static_int_cast< sal_Int32 >(n));
+}
+}
+
+static void ImplMakeConfigList( ImplConfigData* pData,
+ const sal_uInt8* pBuf, sal_uInt64 nLen )
+{
+ if ( !nLen )
+ return;
+
+ // Parse buffer and build config list
+ sal_uInt64 nStart;
+ sal_uInt64 nLineLen;
+ sal_uInt64 nNameLen;
+ sal_uInt64 nKeyLen;
+ sal_uInt64 i;
+ const sal_uInt8* pLine;
+ ImplKeyData* pPrevKey = nullptr;
+ ImplGroupData* pPrevGroup = nullptr;
+ ImplGroupData* pGroup = nullptr;
+ i = 0;
+ while ( i < nLen )
+ {
+ // Ctrl+Z
+ if ( pBuf[i] == 0x1A )
+ break;
+
+ // Remove spaces and tabs
+ while ( (pBuf[i] == ' ') || (pBuf[i] == '\t') )
+ i++;
+
+ // remember line-starts
+ nStart = i;
+ pLine = pBuf+i;
+
+ // search line-endings
+ while ( (i < nLen) && pBuf[i] && (pBuf[i] != '\r') && (pBuf[i] != '\n') &&
+ (pBuf[i] != 0x1A) )
+ i++;
+
+ nLineLen = i-nStart;
+
+ // if Line-ending is found, continue once
+ if ( (i+1 < nLen) &&
+ (pBuf[i] != pBuf[i+1]) &&
+ ((pBuf[i+1] == '\r') || (pBuf[i+1] == '\n')) )
+ i++;
+ i++;
+
+ // evaluate line
+ if ( *pLine == '[' )
+ {
+ pGroup = new ImplGroupData;
+ pGroup->mpNext = nullptr;
+ pGroup->mpFirstKey = nullptr;
+ pGroup->mnEmptyLines = 0;
+ if ( pPrevGroup )
+ pPrevGroup->mpNext = pGroup;
+ else
+ pData->mpFirstGroup = pGroup;
+ pPrevGroup = pGroup;
+ pPrevKey = nullptr;
+
+ // filter group names
+ pLine++;
+ nLineLen--;
+ // remove spaces and tabs
+ while ( (*pLine == ' ') || (*pLine == '\t') )
+ {
+ nLineLen--;
+ pLine++;
+ }
+ nNameLen = 0;
+ while ( (nNameLen < nLineLen) && (pLine[nNameLen] != ']') )
+ nNameLen++;
+ if ( nNameLen )
+ {
+ while ( (pLine[nNameLen-1] == ' ') || (pLine[nNameLen-1] == '\t') )
+ nNameLen--;
+ }
+ pGroup->maGroupName = makeOString(pLine, nNameLen);
+ }
+ else
+ {
+ if ( nLineLen )
+ {
+ // If no group exists yet, add to default
+ if ( !pGroup )
+ {
+ pGroup = new ImplGroupData;
+ pGroup->mpNext = nullptr;
+ pGroup->mpFirstKey = nullptr;
+ pGroup->mnEmptyLines = 0;
+ pData->mpFirstGroup = pGroup;
+ pPrevGroup = pGroup;
+ pPrevKey = nullptr;
+ }
+
+ // if empty line, append it
+ if ( pPrevKey )
+ {
+ while ( pGroup->mnEmptyLines )
+ {
+ ImplKeyData* pKey = new ImplKeyData;
+ pKey->mbIsComment = true;
+ pPrevKey->mpNext = pKey;
+ pPrevKey = pKey;
+ pGroup->mnEmptyLines--;
+ }
+ }
+
+ // Generate new key
+ ImplKeyData* pKey = new ImplKeyData;
+ pKey->mpNext = nullptr;
+ if ( pPrevKey )
+ pPrevKey->mpNext = pKey;
+ else
+ pGroup->mpFirstKey = pKey;
+ pPrevKey = pKey;
+ if ( pLine[0] == ';' )
+ {
+ pKey->maValue = makeOString(pLine, nLineLen);
+ pKey->mbIsComment = true;
+ }
+ else
+ {
+ pKey->mbIsComment = false;
+ nNameLen = 0;
+ while ( (nNameLen < nLineLen) && (pLine[nNameLen] != '=') )
+ nNameLen++;
+ nKeyLen = nNameLen;
+ // Remove spaces and tabs
+ if ( nNameLen )
+ {
+ while ( (pLine[nNameLen-1] == ' ') || (pLine[nNameLen-1] == '\t') )
+ nNameLen--;
+ }
+ pKey->maKey = makeOString(pLine, nNameLen);
+ nKeyLen++;
+ if ( nKeyLen < nLineLen )
+ {
+ pLine += nKeyLen;
+ nLineLen -= nKeyLen;
+ // Remove spaces and tabs
+ while ( (*pLine == ' ') || (*pLine == '\t') )
+ {
+ nLineLen--;
+ pLine++;
+ }
+ if ( nLineLen )
+ {
+ while ( (pLine[nLineLen-1] == ' ') || (pLine[nLineLen-1] == '\t') )
+ nLineLen--;
+ pKey->maValue = makeOString(pLine, nLineLen);
+ }
+ }
+ }
+ }
+ else
+ {
+ // Spaces are counted and appended only after key generation,
+ // as we want to store spaces even after adding new keys
+ if ( pGroup )
+ pGroup->mnEmptyLines++;
+ }
+ }
+ }
+}
+
+static std::unique_ptr<sal_uInt8[]> ImplGetConfigBuffer( const ImplConfigData* pData, sal_uInt32& rLen )
+{
+ std::unique_ptr<sal_uInt8[]> pWriteBuf;
+ sal_uInt8* pBuf;
+ sal_uInt8 aLineEndBuf[2] = {0, 0};
+ ImplKeyData* pKey;
+ ImplGroupData* pGroup;
+ sal_uInt32 nBufLen;
+ sal_uInt32 nValueLen;
+ sal_uInt32 nKeyLen;
+ sal_uInt32 nLineEndLen;
+
+ aLineEndBuf[0] = '\r';
+ aLineEndBuf[1] = '\n';
+ nLineEndLen = 2;
+
+ nBufLen = 0;
+ pGroup = pData->mpFirstGroup;
+ while ( pGroup )
+ {
+ // Don't write empty groups
+ if ( pGroup->mpFirstKey )
+ {
+ nBufLen += pGroup->maGroupName.getLength() + nLineEndLen + 2;
+ pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ nValueLen = pKey->maValue.getLength();
+ if ( pKey->mbIsComment )
+ nBufLen += nValueLen + nLineEndLen;
+ else
+ nBufLen += pKey->maKey.getLength() + nValueLen + nLineEndLen + 1;
+
+ pKey = pKey->mpNext;
+ }
+
+ // Write empty lines after each group
+ if ( !pGroup->mnEmptyLines )
+ pGroup->mnEmptyLines = 1;
+ nBufLen += nLineEndLen * pGroup->mnEmptyLines;
+ }
+
+ pGroup = pGroup->mpNext;
+ }
+
+ // Output buffer length
+ rLen = nBufLen;
+ if ( !nBufLen )
+ {
+ pWriteBuf.reset(new sal_uInt8[nLineEndLen]);
+ pWriteBuf[0] = aLineEndBuf[0];
+ if ( nLineEndLen == 2 )
+ pWriteBuf[1] = aLineEndBuf[1];
+ return pWriteBuf;
+ }
+
+ // Allocate new write buffer (caller frees it)
+ pWriteBuf.reset(new sal_uInt8[nBufLen]);
+
+ // fill buffer
+ pBuf = pWriteBuf.get();
+ pGroup = pData->mpFirstGroup;
+ while ( pGroup )
+ {
+ // Don't write empty groups
+ if ( pGroup->mpFirstKey )
+ {
+ *pBuf = '['; pBuf++;
+ memcpy( pBuf, pGroup->maGroupName.getStr(), pGroup->maGroupName.getLength() );
+ pBuf += pGroup->maGroupName.getLength();
+ *pBuf = ']'; pBuf++;
+ *pBuf = aLineEndBuf[0]; pBuf++;
+ if ( nLineEndLen == 2 )
+ {
+ *pBuf = aLineEndBuf[1]; pBuf++;
+ }
+ pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ nValueLen = pKey->maValue.getLength();
+ if ( pKey->mbIsComment )
+ {
+ if ( nValueLen )
+ {
+ memcpy( pBuf, pKey->maValue.getStr(), nValueLen );
+ pBuf += nValueLen;
+ }
+ *pBuf = aLineEndBuf[0]; pBuf++;
+ if ( nLineEndLen == 2 )
+ {
+ *pBuf = aLineEndBuf[1]; pBuf++;
+ }
+ }
+ else
+ {
+ nKeyLen = pKey->maKey.getLength();
+ memcpy( pBuf, pKey->maKey.getStr(), nKeyLen );
+ pBuf += nKeyLen;
+ *pBuf = '='; pBuf++;
+ memcpy( pBuf, pKey->maValue.getStr(), nValueLen );
+ pBuf += nValueLen;
+ *pBuf = aLineEndBuf[0]; pBuf++;
+ if ( nLineEndLen == 2 )
+ {
+ *pBuf = aLineEndBuf[1]; pBuf++;
+ }
+ }
+
+ pKey = pKey->mpNext;
+ }
+
+ // Store empty line after each group
+ sal_uInt16 nEmptyLines = pGroup->mnEmptyLines;
+ while ( nEmptyLines )
+ {
+ *pBuf = aLineEndBuf[0]; pBuf++;
+ if ( nLineEndLen == 2 )
+ {
+ *pBuf = aLineEndBuf[1]; pBuf++;
+ }
+ nEmptyLines--;
+ }
+ }
+
+ pGroup = pGroup->mpNext;
+ }
+
+ return pWriteBuf;
+}
+
+static void ImplReadConfig( ImplConfigData* pData )
+{
+ sal_uInt32 nTimeStamp = 0;
+ sal_uInt64 nRead = 0;
+ bool bRead = false;
+ bool bIsUTF8BOM = false;
+ std::unique_ptr<sal_uInt8[]> pBuf = ImplSysReadConfig( pData->maFileName, nRead, bRead, bIsUTF8BOM, nTimeStamp );
+
+ // Read config list from buffer
+ if ( pBuf )
+ {
+ ImplMakeConfigList( pData, pBuf.get(), nRead );
+ pBuf.reset();
+ }
+ pData->mnTimeStamp = nTimeStamp;
+ pData->mbModified = false;
+ if ( bRead )
+ pData->mbRead = true;
+ if ( bIsUTF8BOM )
+ pData->mbIsUTF8BOM = true;
+}
+
+static void ImplWriteConfig( ImplConfigData* pData )
+{
+ SAL_WARN_IF( pData->mnTimeStamp != ImplSysGetConfigTimeStamp( pData->maFileName ),
+ "tools.generic", "Config overwrites modified configfile: " << pData->maFileName );
+
+ // Read config list from buffer
+ sal_uInt32 nBufLen;
+ std::unique_ptr<sal_uInt8[]> pBuf = ImplGetConfigBuffer( pData, nBufLen );
+ if ( pBuf )
+ {
+ if ( ImplSysWriteConfig( pData->maFileName, pBuf.get(), nBufLen, pData->mbIsUTF8BOM, pData->mnTimeStamp ) )
+ pData->mbModified = false;
+ }
+ else
+ pData->mbModified = false;
+}
+
+static void ImplDeleteConfigData( ImplConfigData* pData )
+{
+ ImplKeyData* pTempKey;
+ ImplKeyData* pKey;
+ ImplGroupData* pTempGroup;
+ ImplGroupData* pGroup = pData->mpFirstGroup;
+ while ( pGroup )
+ {
+ pTempGroup = pGroup->mpNext;
+
+ // remove all keys
+ pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ pTempKey = pKey->mpNext;
+ delete pKey;
+ pKey = pTempKey;
+ }
+
+ // remove group and continue
+ delete pGroup;
+ pGroup = pTempGroup;
+ }
+
+ pData->mpFirstGroup = nullptr;
+}
+
+static std::unique_ptr<ImplConfigData> ImplGetConfigData( const OUString& rFileName )
+{
+ std::unique_ptr<ImplConfigData> pData(new ImplConfigData);
+ pData->maFileName = rFileName;
+ pData->mpFirstGroup = nullptr;
+ pData->mnDataUpdateId = 0;
+ pData->mbRead = false;
+ pData->mbIsUTF8BOM = false;
+ ImplReadConfig( pData.get() );
+
+ return pData;
+}
+
+bool Config::ImplUpdateConfig() const
+{
+ // Re-read file if timestamp differs
+ if ( mpData->mnTimeStamp != ImplSysGetConfigTimeStamp( maFileName ) )
+ {
+ ImplDeleteConfigData( mpData.get() );
+ ImplReadConfig( mpData.get() );
+ mpData->mnDataUpdateId++;
+ return true;
+ }
+ else
+ return false;
+}
+
+ImplGroupData* Config::ImplGetGroup() const
+{
+ if ( !mpActGroup || (mnDataUpdateId != mpData->mnDataUpdateId) )
+ {
+ ImplGroupData* pPrevGroup = nullptr;
+ ImplGroupData* pGroup = mpData->mpFirstGroup;
+ while ( pGroup )
+ {
+ if ( pGroup->maGroupName.equalsIgnoreAsciiCase(maGroupName) )
+ break;
+
+ pPrevGroup = pGroup;
+ pGroup = pGroup->mpNext;
+ }
+
+ // Add group if not exists
+ if ( !pGroup )
+ {
+ pGroup = new ImplGroupData;
+ pGroup->mpNext = nullptr;
+ pGroup->mpFirstKey = nullptr;
+ pGroup->mnEmptyLines = 1;
+ if ( pPrevGroup )
+ pPrevGroup->mpNext = pGroup;
+ else
+ mpData->mpFirstGroup = pGroup;
+ }
+
+ // Always inherit group names and update cache members
+ pGroup->maGroupName = maGroupName;
+ const_cast<Config*>(this)->mnDataUpdateId = mpData->mnDataUpdateId;
+ const_cast<Config*>(this)->mpActGroup = pGroup;
+ }
+
+ return mpActGroup;
+}
+
+Config::Config( const OUString& rFileName )
+{
+ // Initialize config data
+ maFileName = toUncPath( rFileName );
+ mpData = ImplGetConfigData( maFileName );
+ mpActGroup = nullptr;
+ mnDataUpdateId = 0;
+
+ SAL_INFO("tools.generic", "Config::Config( " << maFileName << " )");
+}
+
+Config::~Config()
+{
+ SAL_INFO("tools.generic", "Config::~Config()" );
+
+ Flush();
+ ImplDeleteConfigData( mpData.get() );
+}
+
+void Config::SetGroup(const OString& rGroup)
+{
+ // If group is to be reset, it needs to be updated on next call
+ if ( maGroupName != rGroup )
+ {
+ maGroupName = rGroup;
+ mnDataUpdateId = mpData->mnDataUpdateId-1;
+ }
+}
+
+void Config::DeleteGroup(std::string_view rGroup)
+{
+ // Update config data if necessary
+ if ( !mpData->mbRead )
+ {
+ ImplUpdateConfig();
+ mpData->mbRead = true;
+ }
+
+ ImplGroupData* pPrevGroup = nullptr;
+ ImplGroupData* pGroup = mpData->mpFirstGroup;
+ while ( pGroup )
+ {
+ if ( pGroup->maGroupName.equalsIgnoreAsciiCase(rGroup) )
+ break;
+
+ pPrevGroup = pGroup;
+ pGroup = pGroup->mpNext;
+ }
+
+ if ( !pGroup )
+ return;
+
+ // Remove all keys
+ ImplKeyData* pTempKey;
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ pTempKey = pKey->mpNext;
+ delete pKey;
+ pKey = pTempKey;
+ }
+
+ // Rewire pointers and remove group
+ if ( pPrevGroup )
+ pPrevGroup->mpNext = pGroup->mpNext;
+ else
+ mpData->mpFirstGroup = pGroup->mpNext;
+ delete pGroup;
+
+ // Rewrite config data
+ mpData->mbModified = true;
+
+ mnDataUpdateId = mpData->mnDataUpdateId;
+ mpData->mnDataUpdateId++;
+}
+
+OString Config::GetGroupName(sal_uInt16 nGroup) const
+{
+ ImplGroupData* pGroup = mpData->mpFirstGroup;
+ sal_uInt16 nGroupCount = 0;
+ OString aGroupName;
+ while ( pGroup )
+ {
+ if ( nGroup == nGroupCount )
+ {
+ aGroupName = pGroup->maGroupName;
+ break;
+ }
+
+ nGroupCount++;
+ pGroup = pGroup->mpNext;
+ }
+
+ return aGroupName;
+}
+
+sal_uInt16 Config::GetGroupCount() const
+{
+ ImplGroupData* pGroup = mpData->mpFirstGroup;
+ sal_uInt16 nGroupCount = 0;
+ while ( pGroup )
+ {
+ nGroupCount++;
+ pGroup = pGroup->mpNext;
+ }
+
+ return nGroupCount;
+}
+
+bool Config::HasGroup(std::string_view rGroup) const
+{
+ ImplGroupData* pGroup = mpData->mpFirstGroup;
+ bool bRet = false;
+
+ while( pGroup )
+ {
+ if( pGroup->maGroupName.equalsIgnoreAsciiCase(rGroup) )
+ {
+ bRet = true;
+ break;
+ }
+
+ pGroup = pGroup->mpNext;
+ }
+
+ return bRet;
+}
+
+OString Config::ReadKey(const OString& rKey) const
+{
+ return ReadKey(rKey, OString());
+}
+
+OString Config::ReadKey(const OString& rKey, const OString& rDefault) const
+{
+ SAL_INFO("tools.generic", "Config::ReadKey( " << rKey << " ) from " << GetGroup()
+ << " in " << maFileName);
+
+ // Search key, return value if found
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( pGroup )
+ {
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment && pKey->maKey.equalsIgnoreAsciiCase(rKey) )
+ return pKey->maValue;
+
+ pKey = pKey->mpNext;
+ }
+ }
+
+ return rDefault;
+}
+
+void Config::WriteKey(const OString& rKey, const OString& rStr)
+{
+ SAL_INFO("tools.generic", "Config::WriteKey( " << rKey << ", " << rStr << " ) to "
+ << GetGroup() << " in " << maFileName);
+
+ // Update config data if necessary
+ if ( !mpData->mbRead )
+ {
+ ImplUpdateConfig();
+ mpData->mbRead = true;
+ }
+
+ // Search key and update value if found
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( !pGroup )
+ return;
+
+ ImplKeyData* pPrevKey = nullptr;
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment && pKey->maKey.equalsIgnoreAsciiCase(rKey) )
+ break;
+
+ pPrevKey = pKey;
+ pKey = pKey->mpNext;
+ }
+
+ bool bNewValue;
+ if ( !pKey )
+ {
+ pKey = new ImplKeyData;
+ pKey->mpNext = nullptr;
+ pKey->maKey = rKey;
+ pKey->mbIsComment = false;
+ if ( pPrevKey )
+ pPrevKey->mpNext = pKey;
+ else
+ pGroup->mpFirstKey = pKey;
+ bNewValue = true;
+ }
+ else
+ bNewValue = pKey->maValue != rStr;
+
+ if ( bNewValue )
+ {
+ pKey->maValue = rStr;
+
+ mpData->mbModified = true;
+ }
+}
+
+void Config::DeleteKey(std::string_view rKey)
+{
+ // Update config data if necessary
+ if ( !mpData->mbRead )
+ {
+ ImplUpdateConfig();
+ mpData->mbRead = true;
+ }
+
+ // Search key and update value
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( !pGroup )
+ return;
+
+ ImplKeyData* pPrevKey = nullptr;
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment && pKey->maKey.equalsIgnoreAsciiCase(rKey) )
+ break;
+
+ pPrevKey = pKey;
+ pKey = pKey->mpNext;
+ }
+
+ if ( pKey )
+ {
+ // Rewire group pointers and delete
+ if ( pPrevKey )
+ pPrevKey->mpNext = pKey->mpNext;
+ else
+ pGroup->mpFirstKey = pKey->mpNext;
+ delete pKey;
+
+ mpData->mbModified = true;
+ }
+}
+
+sal_uInt16 Config::GetKeyCount() const
+{
+ SAL_INFO("tools.generic", "Config::GetKeyCount() from " << GetGroup() << " in " << maFileName);
+
+ // Search key and update value
+ sal_uInt16 nCount = 0;
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( pGroup )
+ {
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment )
+ nCount++;
+
+ pKey = pKey->mpNext;
+ }
+ }
+
+ return nCount;
+}
+
+OString Config::GetKeyName(sal_uInt16 nKey) const
+{
+ SAL_INFO("tools.generic", "Config::GetKeyName( " << OString::number(static_cast<sal_Int32>(nKey))
+ << " ) from " << GetGroup() << " in " << maFileName);
+
+ // search key and return name if found
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( pGroup )
+ {
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment )
+ {
+ if ( !nKey )
+ return pKey->maKey;
+ nKey--;
+ }
+
+ pKey = pKey->mpNext;
+ }
+ }
+
+ return OString();
+}
+
+OString Config::ReadKey(sal_uInt16 nKey) const
+{
+ SAL_INFO("tools.generic", "Config::ReadKey( " << OString::number(static_cast<sal_Int32>(nKey))
+ << " ) from " << GetGroup() << " in " << maFileName);
+
+ // Search key and return value if found
+ ImplGroupData* pGroup = ImplGetGroup();
+ if ( pGroup )
+ {
+ ImplKeyData* pKey = pGroup->mpFirstKey;
+ while ( pKey )
+ {
+ if ( !pKey->mbIsComment )
+ {
+ if ( !nKey )
+ return pKey->maValue;
+ nKey--;
+ }
+
+ pKey = pKey->mpNext;
+ }
+ }
+
+ return OString();
+}
+
+void Config::Flush()
+{
+ if ( mpData->mbModified )
+ ImplWriteConfig( mpData.get() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/fract.cxx b/tools/source/generic/fract.cxx
new file mode 100644
index 0000000000..abe8ee41b2
--- /dev/null
+++ b/tools/source/generic/fract.cxx
@@ -0,0 +1,537 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/fract.hxx>
+#include <tools/debug.hxx>
+#include <o3tl/hash_combine.hxx>
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <algorithm>
+#include <cmath>
+#include <numeric>
+
+#include <boost/rational.hpp>
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+static boost::rational<sal_Int32> rational_FromDouble(double dVal);
+static void rational_ReduceInaccurate(boost::rational<sal_Int32>& rRational, unsigned nSignificantBits);
+static int impl_NumberOfBits( sal_uInt32 nNum );
+
+static boost::rational<sal_Int32> toRational(sal_Int32 n, sal_Int32 d)
+{
+ // https://github.com/boostorg/boost/issues/335 when these are std::numeric_limits<sal_Int32>::min
+ if (n == d)
+ return 1;
+ // tdf#144319 avoid boost::bad_rational e.g. if numerator=-476741369, denominator=-2147483648
+ if (d < -std::numeric_limits<sal_Int32>::max())
+ return 0;
+ return boost::rational<sal_Int32>(n, d);
+}
+
+static constexpr bool isOutOfRange(sal_Int64 nNum)
+{
+ return nNum < std::numeric_limits<sal_Int32>::min()
+ || nNum > std::numeric_limits<sal_Int32>::max();
+}
+
+Fraction::Fraction( sal_Int64 nNum, sal_Int64 nDen ) : mnNumerator(nNum), mnDenominator(nDen)
+{
+ if ( isOutOfRange(nNum) || isOutOfRange(nDen) )
+ {
+ // tdf#143200
+ if (const auto gcd = std::gcd(nNum, nDen); gcd > 1)
+ {
+ nNum /= gcd;
+ nDen /= gcd;
+ }
+ SAL_WARN_IF(isOutOfRange(nNum) || isOutOfRange(nDen),
+ "tools.fraction", "values outside of range we can represent, doing reduction, which will reduce precision");
+ while (isOutOfRange(nNum) || isOutOfRange(nDen))
+ {
+ nNum /= 2;
+ nDen /= 2;
+ }
+ mnNumerator = nNum;
+ mnDenominator = nDen;
+ }
+ if ( mnDenominator == 0 )
+ {
+ mbValid = false;
+ SAL_WARN( "tools.fraction", "'Fraction(" << nNum << ",0)' invalid fraction created" );
+ return;
+ }
+ else if ((nDen == -1 && nNum == std::numeric_limits<sal_Int32>::min()) ||
+ (nNum == -1 && nDen == std::numeric_limits<sal_Int32>::min()))
+ {
+ mbValid = false;
+ SAL_WARN("tools.fraction", "'Fraction(" << nNum << "," << nDen << ")' invalid fraction created");
+ return;
+ }
+}
+
+/**
+ * only here to prevent passing of NaN
+ */
+Fraction::Fraction( double nNum, double nDen )
+ : Fraction(sal_Int64(nNum), sal_Int64(nDen))
+{}
+
+Fraction::Fraction( double dVal )
+{
+ try
+ {
+ boost::rational<sal_Int32> v = rational_FromDouble( dVal );
+ mnNumerator = v.numerator();
+ mnDenominator = v.denominator();
+ }
+ catch (const boost::bad_rational&)
+ {
+ mbValid = false;
+ SAL_WARN( "tools.fraction", "'Fraction(" << dVal << ")' invalid fraction created" );
+ }
+}
+
+Fraction::operator double() const
+{
+ if (!mbValid)
+ {
+ SAL_WARN( "tools.fraction", "'double()' on invalid fraction" );
+ return 0.0;
+ }
+
+ return boost::rational_cast<double>(toRational(mnNumerator, mnDenominator));
+}
+
+// This methods first validates both values.
+// If one of the arguments is invalid, the whole operation is invalid.
+// After computation detect if result overflows a sal_Int32 value
+// which cause the operation to be marked as invalid
+Fraction& Fraction::operator += ( const Fraction& rVal )
+{
+ if ( !rVal.mbValid )
+ mbValid = false;
+
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator +=' with invalid fraction" );
+ return *this;
+ }
+
+ boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
+ a += toRational(rVal.mnNumerator, rVal.mnDenominator);
+ mnNumerator = a.numerator();
+ mnDenominator = a.denominator();
+
+ return *this;
+}
+
+Fraction& Fraction::operator -= ( const Fraction& rVal )
+{
+ if ( !rVal.mbValid )
+ mbValid = false;
+
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator -=' with invalid fraction" );
+ return *this;
+ }
+
+ boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
+ a -= toRational(rVal.mnNumerator, rVal.mnDenominator);
+ mnNumerator = a.numerator();
+ mnDenominator = a.denominator();
+
+ return *this;
+}
+
+namespace
+{
+ bool checked_multiply_by(boost::rational<sal_Int32>& i, const boost::rational<sal_Int32>& r)
+ {
+ // Protect against self-modification
+ sal_Int32 num = r.numerator();
+ sal_Int32 den = r.denominator();
+
+ // Fast-path if the number of bits in input is < the number of bits in the output, overflow cannot happen
+ // This is considerably faster than repeated std::gcd() operations
+ if ((impl_NumberOfBits(std::abs(i.numerator())) + impl_NumberOfBits(std::abs(r.numerator()))) < 32 &&
+ (impl_NumberOfBits(std::abs(i.denominator())) + impl_NumberOfBits(std::abs(r.denominator()))) < 32)
+ {
+ i *= r;
+ return false;
+ }
+
+ // Avoid overflow and preserve normalization
+ sal_Int32 gcd1 = std::gcd(i.numerator(), den);
+ sal_Int32 gcd2 = std::gcd(num, i.denominator());
+
+ if (!gcd1 || !gcd2)
+ return true;
+
+ bool fail = false;
+ fail |= o3tl::checked_multiply(i.numerator() / gcd1, num / gcd2, num);
+ fail |= o3tl::checked_multiply(i.denominator() / gcd2, den / gcd1, den);
+
+ if (!fail)
+ i.assign(num, den);
+
+ return fail;
+ }
+}
+
+Fraction& Fraction::operator *= ( const Fraction& rVal )
+{
+ if ( !rVal.mbValid )
+ mbValid = false;
+
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator *=' with invalid fraction" );
+ return *this;
+ }
+
+ boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
+ boost::rational<sal_Int32> b = toRational(rVal.mnNumerator, rVal.mnDenominator);
+ bool bFail = checked_multiply_by(a, b);
+ mnNumerator = a.numerator();
+ mnDenominator = a.denominator();
+
+ if (bFail)
+ {
+ mbValid = false;
+ }
+
+ return *this;
+}
+
+Fraction& Fraction::operator /= ( const Fraction& rVal )
+{
+ if ( !rVal.mbValid )
+ mbValid = false;
+
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator /=' with invalid fraction" );
+ return *this;
+ }
+
+ boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
+ a /= toRational(rVal.mnNumerator, rVal.mnDenominator);
+ mnNumerator = a.numerator();
+ mnDenominator = a.denominator();
+
+ return *this;
+}
+
+/** Inaccurate cancellation for a fraction.
+
+ Clip both nominator and denominator to said number of bits. If
+ either of those already have equal or less number of bits used,
+ this method does nothing.
+
+ @param nSignificantBits denotes, how many significant binary
+ digits to maintain, in both nominator and denominator.
+
+ @example ReduceInaccurate(8) has an error <1% [1/2^(8-1)] - the
+ largest error occurs with the following pair of values:
+
+ binary 1000000011111111111111111111111b/1000000000000000000000000000000b
+ = 1082130431/1073741824
+ = approx. 1.007812499
+
+ A ReduceInaccurate(8) yields 1/1.
+*/
+void Fraction::ReduceInaccurate( unsigned nSignificantBits )
+{
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'ReduceInaccurate' on invalid fraction" );
+ return;
+ }
+
+ if ( !mnNumerator )
+ return;
+
+ auto a = toRational(mnNumerator, mnDenominator);
+ rational_ReduceInaccurate(a, nSignificantBits);
+ mnNumerator = a.numerator();
+ mnDenominator = a.denominator();
+}
+
+sal_Int32 Fraction::GetNumerator() const
+{
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'GetNumerator()' on invalid fraction" );
+ return 0;
+ }
+ return mnNumerator;
+}
+
+sal_Int32 Fraction::GetDenominator() const
+{
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'GetDenominator()' on invalid fraction" );
+ return -1;
+ }
+ return mnDenominator;
+}
+
+Fraction::operator sal_Int32() const
+{
+ if ( !mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator sal_Int32()' on invalid fraction" );
+ return 0;
+ }
+ return boost::rational_cast<sal_Int32>(toRational(mnNumerator, mnDenominator));
+}
+
+Fraction operator+( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ Fraction aErg( rVal1 );
+ aErg += rVal2;
+ return aErg;
+}
+
+Fraction operator-( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ Fraction aErg( rVal1 );
+ aErg -= rVal2;
+ return aErg;
+}
+
+Fraction operator*( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ Fraction aErg( rVal1 );
+ aErg *= rVal2;
+ return aErg;
+}
+
+Fraction operator/( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ Fraction aErg( rVal1 );
+ aErg /= rVal2;
+ return aErg;
+}
+
+bool operator !=( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ return !(rVal1 == rVal2);
+}
+
+bool operator <=( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ return !(rVal1 > rVal2);
+}
+
+bool operator >=( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ return !(rVal1 < rVal2);
+}
+
+bool operator == ( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ if ( !rVal1.mbValid || !rVal2.mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator ==' with an invalid fraction" );
+ return false;
+ }
+
+ return toRational(rVal1.mnNumerator, rVal1.mnDenominator) == toRational(rVal2.mnNumerator, rVal2.mnDenominator);
+}
+
+bool operator < ( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ if ( !rVal1.mbValid || !rVal2.mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator <' with an invalid fraction" );
+ return false;
+ }
+
+ return toRational(rVal1.mnNumerator, rVal1.mnDenominator) < toRational(rVal2.mnNumerator, rVal2.mnDenominator);
+}
+
+bool operator > ( const Fraction& rVal1, const Fraction& rVal2 )
+{
+ if ( !rVal1.mbValid || !rVal2.mbValid )
+ {
+ SAL_WARN( "tools.fraction", "'operator >' with an invalid fraction" );
+ return false;
+ }
+
+ return toRational(rVal1.mnNumerator, rVal1.mnDenominator) > toRational(rVal2.mnNumerator, rVal2.mnDenominator);
+}
+
+// If dVal > LONG_MAX or dVal < LONG_MIN, the rational throws a boost::bad_rational.
+// Otherwise, dVal and denominator are multiplied by 10, until one of them
+// is larger than (LONG_MAX / 10).
+//
+// NOTE: here we use 'sal_Int32' due that only values in sal_Int32 range are valid.
+static boost::rational<sal_Int32> rational_FromDouble(double dVal)
+{
+ if ( dVal > std::numeric_limits<sal_Int32>::max() ||
+ dVal < std::numeric_limits<sal_Int32>::min() ||
+ std::isnan(dVal) )
+ throw boost::bad_rational();
+
+ const sal_Int32 nMAX = std::numeric_limits<sal_Int32>::max() / 10;
+ sal_Int32 nDen = 1;
+ while ( std::abs( dVal ) < nMAX && nDen < nMAX ) {
+ dVal *= 10;
+ nDen *= 10;
+ }
+ return boost::rational<sal_Int32>( sal_Int32(dVal), nDen );
+}
+
+/**
+ * Find the number of bits required to represent this number, using the CLZ intrinsic
+ */
+static int impl_NumberOfBits( sal_uInt32 nNum )
+{
+ if (nNum == 0)
+ return 0;
+#ifdef _MSC_VER
+ unsigned long r = 0;
+ _BitScanReverse(&r, nNum);
+ return r + 1;
+#else
+ return 32 - __builtin_clz(nNum);
+#endif
+}
+
+/** Inaccurate cancellation for a fraction.
+
+ Clip both nominator and denominator to said number of bits. If
+ either of those already have equal or less number of bits used,
+ this method does nothing.
+
+ @param nSignificantBits denotes, how many significant binary
+ digits to maintain, in both nominator and denominator.
+
+ @example ReduceInaccurate(8) has an error <1% [1/2^(8-1)] - the
+ largest error occurs with the following pair of values:
+
+ binary 1000000011111111111111111111111b/1000000000000000000000000000000b
+ = 1082130431/1073741824
+ = approx. 1.007812499
+
+ A ReduceInaccurate(8) yields 1/1.
+*/
+static void rational_ReduceInaccurate(boost::rational<sal_Int32>& rRational, unsigned nSignificantBits)
+{
+ if ( !rRational )
+ return;
+
+ // http://www.boost.org/doc/libs/release/libs/rational/rational.html#Internal%20representation
+ sal_Int32 nMul = rRational.numerator();
+ if (nMul == std::numeric_limits<sal_Int32>::min())
+ {
+ // ofz#32973 Integer-overflow
+ return;
+ }
+ const bool bNeg = nMul < 0;
+ if (bNeg)
+ nMul = -nMul;
+ sal_Int32 nDiv = rRational.denominator();
+
+ DBG_ASSERT(nSignificantBits<65, "More than 64 bit of significance is overkill!");
+
+ // How much bits can we lose?
+ const int nMulBitsToLose = std::max( ( impl_NumberOfBits( nMul ) - int( nSignificantBits ) ), 0 );
+ const int nDivBitsToLose = std::max( ( impl_NumberOfBits( nDiv ) - int( nSignificantBits ) ), 0 );
+
+ const int nToLose = std::min( nMulBitsToLose, nDivBitsToLose );
+
+ // Remove the bits
+ nMul >>= nToLose;
+ nDiv >>= nToLose;
+
+ if ( !nMul || !nDiv ) {
+ // Return without reduction
+ OSL_FAIL( "Oops, we reduced too much..." );
+ return;
+ }
+
+ rRational.assign( bNeg ? -nMul : nMul, nDiv );
+}
+
+size_t Fraction::GetHashValue() const
+{
+ size_t hash = 0;
+ o3tl::hash_combine( hash, mnNumerator );
+ o3tl::hash_combine( hash, mnDenominator );
+ o3tl::hash_combine( hash, mbValid );
+ return hash;
+}
+
+Fraction Fraction::MakeFraction( tools::Long nN1, tools::Long nN2, tools::Long nD1, tools::Long nD2 )
+{
+ if( nD1 == 0 || nD2 == 0 ) //under these bad circumstances the following while loop will be endless
+ {
+ SAL_WARN("tools.fraction", "Invalid parameter for ImplMakeFraction");
+ return Fraction( 1, 1 );
+ }
+
+ tools::Long i = 1;
+
+ if ( nN1 < 0 ) { i = -i; nN1 = -nN1; }
+ if ( nN2 < 0 ) { i = -i; nN2 = -nN2; }
+ if ( nD1 < 0 ) { i = -i; nD1 = -nD1; }
+ if ( nD2 < 0 ) { i = -i; nD2 = -nD2; }
+ // all positive; i sign
+
+ assert( nN1 >= std::numeric_limits<sal_Int32>::min() );
+ assert( nN1 <= std::numeric_limits<sal_Int32>::max( ));
+ assert( nD1 >= std::numeric_limits<sal_Int32>::min() );
+ assert( nD1 <= std::numeric_limits<sal_Int32>::max( ));
+ assert( nN2 >= std::numeric_limits<sal_Int32>::min() );
+ assert( nN2 <= std::numeric_limits<sal_Int32>::max( ));
+ assert( nD2 >= std::numeric_limits<sal_Int32>::min() );
+ assert( nD2 <= std::numeric_limits<sal_Int32>::max( ));
+
+ boost::rational<sal_Int32> a = toRational(i*nN1, nD1);
+ boost::rational<sal_Int32> b = toRational(nN2, nD2);
+ bool bFail = checked_multiply_by(a, b);
+
+ while ( bFail ) {
+ if ( nN1 > nN2 )
+ nN1 = (nN1 + 1) / 2;
+ else
+ nN2 = (nN2 + 1) / 2;
+ if ( nD1 > nD2 )
+ nD1 = (nD1 + 1) / 2;
+ else
+ nD2 = (nD2 + 1) / 2;
+
+ a = toRational(i*nN1, nD1);
+ b = toRational(nN2, nD2);
+ bFail = checked_multiply_by(a, b);
+ }
+
+ return Fraction(a.numerator(), a.denominator());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/gen.cxx b/tools/source/generic/gen.cxx
new file mode 100644
index 0000000000..012318c021
--- /dev/null
+++ b/tools/source/generic/gen.cxx
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <rtl/string.hxx>
+
+#include <algorithm>
+#include <tuple>
+#include <o3tl/hash_combine.hxx>
+#include <o3tl/safeint.hxx>
+#include <tools/gen.hxx>
+
+OString Pair::toString() const
+{
+ // Note that this is not just used for debugging output but the
+ // format is parsed by external code (passed in callbacks to
+ // LibreOfficeKit clients). So don't change.
+ return OString::number(A()) + ", " + OString::number(B());
+}
+
+size_t Pair::GetHashValue() const
+{
+ size_t hash = 0;
+ o3tl::hash_combine( hash, mnA );
+ o3tl::hash_combine( hash, mnB );
+ return hash;
+}
+
+void RectangleTemplateBase::SaturatingSetSize(const SizeTemplateBase& rSize)
+{
+ if (rSize.Width() < 0)
+ mnRight = o3tl::saturating_add(mnLeft, (rSize.Width() + 1));
+ else if ( rSize.Width() > 0 )
+ mnRight = o3tl::saturating_add(mnLeft, (rSize.Width() - 1));
+ else
+ SetWidthEmpty();
+
+ if ( rSize.Height() < 0 )
+ mnBottom = o3tl::saturating_add(mnTop, (rSize.Height() + 1));
+ else if ( rSize.Height() > 0 )
+ mnBottom = o3tl::saturating_add(mnTop, (rSize.Height() - 1));
+ else
+ SetHeightEmpty();
+}
+
+void RectangleTemplateBase::SaturatingSetPosX(tools::Long x)
+{
+ if (!IsWidthEmpty())
+ mnRight = o3tl::saturating_add(mnRight, x - mnLeft);
+ mnLeft = x;
+}
+
+void RectangleTemplateBase::SaturatingSetPosY(tools::Long y)
+{
+ if (!IsHeightEmpty())
+ mnBottom = o3tl::saturating_add(mnBottom, y - mnTop);
+ mnTop = y;
+}
+
+void RectangleTemplateBase::Union( const RectangleTemplateBase& rRect )
+{
+ if ( rRect.IsEmpty() )
+ return;
+
+ if ( IsEmpty() )
+ *this = rRect;
+ else
+ {
+ std::tie(mnLeft, mnRight) = std::minmax({ mnLeft, rRect.mnLeft, mnRight, rRect.mnRight });
+ std::tie(mnTop, mnBottom) = std::minmax({ mnTop, rRect.mnTop, mnBottom, rRect.mnBottom });
+ }
+}
+
+void RectangleTemplateBase::Intersection( const RectangleTemplateBase& rRect )
+{
+ if ( IsEmpty() )
+ return;
+ if ( rRect.IsEmpty() )
+ {
+ *this = tools::Rectangle();
+ return;
+ }
+
+ // Normalize rectangle
+ RectangleTemplateBase aTmpRect( rRect );
+ Normalize();
+ aTmpRect.Normalize();
+
+ // Perform intersection
+ mnLeft = std::max( mnLeft, aTmpRect.mnLeft );
+ mnRight = std::min( mnRight, aTmpRect.mnRight );
+ mnTop = std::max( mnTop, aTmpRect.mnTop );
+ mnBottom= std::min( mnBottom, aTmpRect.mnBottom );
+
+ // Determine if intersection is empty
+ if ( mnRight < mnLeft || mnBottom < mnTop )
+ *this = tools::Rectangle();
+}
+
+void RectangleTemplateBase::Normalize()
+{
+ if ((mnRight < mnLeft) && (!IsWidthEmpty()))
+ {
+ std::swap(mnLeft, mnRight);
+ }
+
+ if ((mnBottom < mnTop) && (!IsHeightEmpty()))
+ {
+ std::swap(mnBottom, mnTop);
+ }
+}
+
+bool RectangleTemplateBase::Contains( const PointTemplateBase& rPoint ) const
+{
+ if ( IsEmpty() )
+ return false;
+
+ if ( mnLeft <= mnRight )
+ {
+ if ( (rPoint.X() < mnLeft) || (rPoint.X() > mnRight) )
+ return false;
+ }
+ else
+ {
+ if ( (rPoint.X() > mnLeft) || (rPoint.X() < mnRight) )
+ return false;
+ }
+ if ( mnTop <= mnBottom )
+ {
+ if ( (rPoint.Y() < mnTop) || (rPoint.Y() > mnBottom) )
+ return false;
+ }
+ else
+ {
+ if ( (rPoint.Y() > mnTop) || (rPoint.Y() < mnBottom) )
+ return false;
+ }
+ return true;
+}
+
+bool RectangleTemplateBase::Contains( const RectangleTemplateBase& rRect ) const
+{
+ return Contains( PointTemplateBase{ rRect.Left(), rRect.Top() } )
+ && Contains( PointTemplateBase{ rRect.Right(), rRect.Bottom() } );
+}
+
+bool RectangleTemplateBase::Overlaps( const RectangleTemplateBase& rRect ) const
+{
+ // If there's no intersection, they don't overlap
+ RectangleTemplateBase aTmp(*this);
+ aTmp.Intersection(rRect);
+ return !aTmp.IsEmpty();
+}
+
+OString RectangleTemplateBase::toString() const
+{
+ // Note that this is not just used for debugging output but the
+ // format is parsed by external code (passed in callbacks to
+ // LibreOfficeKit clients). So don't change.
+ return OString::number(Left()) + ", "
+ + OString::number(Top()) + ", "
+ + OString::number(getOpenWidth()) + ", "
+ + OString::number(getOpenHeight());
+}
+
+void RectangleTemplateBase::expand(tools::Long nExpandBy)
+{
+ AdjustLeft(-nExpandBy);
+ AdjustTop(-nExpandBy);
+ AdjustRight(nExpandBy);
+ AdjustBottom(nExpandBy);
+}
+
+void RectangleTemplateBase::shrink(tools::Long nShrinkBy)
+{
+ mnLeft += nShrinkBy;
+ mnTop += nShrinkBy;
+ if (!IsWidthEmpty())
+ mnRight -= nShrinkBy;
+ if (!IsHeightEmpty())
+ mnBottom -= nShrinkBy;
+}
+
+tools::Long RectangleTemplateBase::AdjustRight(tools::Long nHorzMoveDelta)
+{
+ if (IsWidthEmpty())
+ mnRight = mnLeft + nHorzMoveDelta - 1;
+ else
+ mnRight += nHorzMoveDelta;
+ return mnRight;
+}
+
+tools::Long RectangleTemplateBase::AdjustBottom( tools::Long nVertMoveDelta )
+{
+ if (IsHeightEmpty())
+ mnBottom = mnTop + nVertMoveDelta - 1;
+ else
+ mnBottom += nVertMoveDelta;
+ return mnBottom;
+}
+
+static_assert( std::is_trivially_copyable< Pair >::value );
+static_assert( std::is_trivially_copyable< Point >::value );
+static_assert( std::is_trivially_copyable< Size >::value );
+static_assert( std::is_trivially_copyable< Range >::value );
+static_assert( std::is_trivially_copyable< Selection >::value );
+static_assert( std::is_trivially_copyable< tools::Rectangle >::value );
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/line.cxx b/tools/source/generic/line.cxx
new file mode 100644
index 0000000000..ee9ad97979
--- /dev/null
+++ b/tools/source/generic/line.cxx
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/line.hxx>
+#include <tools/helpers.hxx>
+
+#include <cmath>
+
+namespace tools
+{
+
+double Line::GetLength() const
+{
+ return hypot( maStart.X() - maEnd.X(), maStart.Y() - maEnd.Y() );
+}
+
+bool Line::Intersection( const Line& rLine, Point& rIntersection ) const
+{
+ double fX, fY;
+ bool bRet;
+
+ if( Intersection( rLine, fX, fY ) )
+ {
+ rIntersection.setX( FRound( fX ) );
+ rIntersection.setY( FRound( fY ) );
+ bRet = true;
+ }
+ else
+ bRet = false;
+
+ return bRet;
+}
+
+bool Line::Intersection( const tools::Line& rLine, double& rIntersectionX, double& rIntersectionY ) const
+{
+ const double fAx = static_cast<double>(maEnd.X()) - maStart.X();
+ const double fAy = static_cast<double>(maEnd.Y()) - maStart.Y();
+ const double fBx = static_cast<double>(rLine.maStart.X()) - rLine.maEnd.X();
+ const double fBy = static_cast<double>(rLine.maStart.Y()) - rLine.maEnd.Y();
+ const double fDen = fAy * fBx - fAx * fBy;
+ bool bOk = false;
+
+ if( fDen != 0. )
+ {
+ const double fCx = static_cast<double>(maStart.X()) - rLine.maStart.X();
+ const double fCy = static_cast<double>(maStart.Y()) - rLine.maStart.Y();
+ const double fA = fBy * fCx - fBx * fCy;
+ const bool bGreater = ( fDen > 0. );
+
+ bOk = true;
+
+ if ( bGreater )
+ {
+ if ( ( fA < 0. ) || ( fA > fDen ) )
+ bOk = false;
+ }
+ else if ( ( fA > 0. ) || ( fA < fDen ) )
+ bOk = false;
+
+ if ( bOk )
+ {
+ const double fB = fAx * fCy - fAy * fCx;
+
+ if ( bGreater )
+ {
+ if ( ( fB < 0. ) || ( fB > fDen ) )
+ bOk = false;
+ }
+ else if ( ( fB > 0. ) || ( fB < fDen ) )
+ bOk = false;
+
+ if( bOk )
+ {
+ const double fAlpha = fA / fDen;
+
+ rIntersectionX = ( maStart.X() + fAlpha * fAx );
+ rIntersectionY = ( maStart.Y() + fAlpha * fAy );
+ }
+ }
+ }
+
+ return bOk;
+}
+
+double Line::GetDistance( const double& rPtX, const double& rPtY ) const
+{
+ double fDist;
+
+ if( maStart != maEnd )
+ {
+ const double fDistX = static_cast<double>(maEnd.X()) - maStart.X();
+ const double fDistY = static_cast<double>(maEnd.Y()) - maStart.Y();
+ const double fACX = static_cast<double>(maStart.X()) - rPtX;
+ const double fACY = static_cast<double>(maStart.Y()) - rPtY;
+ const double fL2 = fDistX * fDistX + fDistY * fDistY;
+ const double fR = ( fACY * -fDistY - fACX * fDistX ) / fL2;
+ const double fS = ( fACY * fDistX - fACX * fDistY ) / fL2;
+
+ if( fR < 0.0 )
+ {
+ fDist = hypot( maStart.X() - rPtX, maStart.Y() - rPtY );
+
+ if( fS < 0.0 )
+ fDist *= -1.0;
+ }
+ else if( fR <= 1.0 )
+ fDist = fS * sqrt( fL2 );
+ else
+ {
+ fDist = hypot( maEnd.X() - rPtX, maEnd.Y() - rPtY );
+
+ if( fS < 0.0 )
+ fDist *= -1.0;
+ }
+ }
+ else
+ fDist = hypot( maStart.X() - rPtX, maStart.Y() - rPtY );
+
+ return fDist;
+}
+
+} //namespace tools
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/point.cxx b/tools/source/generic/point.cxx
new file mode 100644
index 0000000000..e71f60411a
--- /dev/null
+++ b/tools/source/generic/point.cxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/gen.hxx>
+
+void PointTemplateBase::RotateAround( PointTemplateBase& rPoint,
+ Degree10 nOrientation ) const
+{
+ tools::Long nX = rPoint.mnA;
+ tools::Long nY = rPoint.mnB;
+ RotateAround(nX, nY, nOrientation);
+ rPoint.mnA = nX;
+ rPoint.mnB = nY;
+}
+
+void PointTemplateBase::RotateAround( tools::Long& rX, tools::Long& rY,
+ Degree10 nOrientation ) const
+{
+ const tools::Long nOriginX = mnA;
+ const tools::Long nOriginY = mnB;
+
+ if ( (nOrientation >= 0_deg10) && !(nOrientation % 900_deg10) )
+ {
+ if ( nOrientation >= 3600_deg10 )
+ nOrientation %= 3600_deg10;
+
+ if ( nOrientation )
+ {
+ rX -= nOriginX;
+ rY -= nOriginY;
+
+ if ( nOrientation == 900_deg10 )
+ {
+ tools::Long nTemp = rX;
+ rX = rY;
+ rY = -nTemp;
+ }
+ else if ( nOrientation == 1800_deg10 )
+ {
+ rX = -rX;
+ rY = -rY;
+ }
+ else /* ( nOrientation == 2700 ) */
+ {
+ tools::Long nTemp = rX;
+ rX = -rY;
+ rY = nTemp;
+ }
+
+ rX += nOriginX;
+ rY += nOriginY;
+ }
+ }
+ else
+ {
+ double nRealOrientation = toRadians(nOrientation);
+ double nCos = cos( nRealOrientation );
+ double nSin = sin( nRealOrientation );
+
+ // Translation...
+ tools::Long nX = rX-nOriginX;
+ tools::Long nY = rY-nOriginY;
+
+ // Rotation...
+ rX = + static_cast<tools::Long>(nCos*nX + nSin*nY) + nOriginX;
+ rY = - static_cast<tools::Long>(nSin*nX - nCos*nY) + nOriginY;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/poly.cxx b/tools/source/generic/poly.cxx
new file mode 100644
index 0000000000..89405eea41
--- /dev/null
+++ b/tools/source/generic/poly.cxx
@@ -0,0 +1,1866 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <osl/endian.h>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <tools/bigint.hxx>
+#include <tools/debug.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/gen.hxx>
+#include <poly.h>
+#include <o3tl/safeint.hxx>
+#include <tools/line.hxx>
+#include <tools/poly.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+
+#include <memory>
+#include <vector>
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <limits.h>
+#include <cmath>
+
+constexpr int EDGE_LEFT = 1;
+constexpr int EDGE_TOP = 2;
+constexpr int EDGE_RIGHT = 4;
+constexpr int EDGE_BOTTOM = 8;
+constexpr int EDGE_HORZ = EDGE_RIGHT | EDGE_LEFT;
+constexpr int EDGE_VERT = EDGE_TOP | EDGE_BOTTOM;
+constexpr double SMALL_DVALUE = 0.0000001;
+#define FSQRT2 1.4142135623730950488016887242097
+
+static double ImplGetParameter( const Point& rCenter, const Point& rPt, double fWR, double fHR )
+{
+ const double nDX = static_cast<double>(rPt.X()) - rCenter.X();
+ const double nDY = static_cast<double>(rCenter.Y()) - rPt.Y();
+ double fAngle = atan2(nDY, nDX);
+
+ return atan2(fWR*sin(fAngle), fHR*cos(fAngle));
+}
+
+ImplPolygon::ImplPolygon( sal_uInt16 nInitSize )
+{
+ ImplInitSize(nInitSize, false);
+}
+
+ImplPolygon::ImplPolygon( const ImplPolygon& rImpPoly )
+{
+ if ( rImpPoly.mnPoints )
+ {
+ mxPointAry.reset(new Point[rImpPoly.mnPoints]);
+ memcpy(mxPointAry.get(), rImpPoly.mxPointAry.get(), rImpPoly.mnPoints * sizeof(Point));
+
+ if( rImpPoly.mxFlagAry )
+ {
+ mxFlagAry.reset(new PolyFlags[rImpPoly.mnPoints]);
+ memcpy(mxFlagAry.get(), rImpPoly.mxFlagAry.get(), rImpPoly.mnPoints);
+ }
+ }
+
+ mnPoints = rImpPoly.mnPoints;
+}
+
+ImplPolygon::ImplPolygon( sal_uInt16 nInitSize, const Point* pInitAry, const PolyFlags* pInitFlags )
+{
+ if ( nInitSize )
+ {
+ mxPointAry.reset(new Point[nInitSize]);
+ memcpy(mxPointAry.get(), pInitAry, nInitSize * sizeof(Point));
+
+ if( pInitFlags )
+ {
+ mxFlagAry.reset(new PolyFlags[nInitSize]);
+ memcpy(mxFlagAry.get(), pInitFlags, nInitSize);
+ }
+ }
+
+ mnPoints = nInitSize;
+}
+
+ImplPolygon::ImplPolygon( const tools::Rectangle& rRect )
+{
+ if ( !rRect.IsEmpty() )
+ {
+ ImplInitSize(5);
+ mxPointAry[0] = rRect.TopLeft();
+ mxPointAry[1] = rRect.TopRight();
+ mxPointAry[2] = rRect.BottomRight();
+ mxPointAry[3] = rRect.BottomLeft();
+ mxPointAry[4] = rRect.TopLeft();
+ }
+ else
+ mnPoints = 0;
+}
+
+ImplPolygon::ImplPolygon( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
+{
+ if ( !rRect.IsEmpty() )
+ {
+ tools::Rectangle aRect( rRect );
+ aRect.Normalize(); // SJ: i9140
+
+ nHorzRound = std::min( nHorzRound, static_cast<sal_uInt32>(std::abs( aRect.GetWidth() >> 1 )) );
+ nVertRound = std::min( nVertRound, static_cast<sal_uInt32>(std::abs( aRect.GetHeight() >> 1 )) );
+
+ if( !nHorzRound && !nVertRound )
+ {
+ ImplInitSize(5);
+ mxPointAry[0] = aRect.TopLeft();
+ mxPointAry[1] = aRect.TopRight();
+ mxPointAry[2] = aRect.BottomRight();
+ mxPointAry[3] = aRect.BottomLeft();
+ mxPointAry[4] = aRect.TopLeft();
+ }
+ else
+ {
+ const Point aTL( aRect.Left() + nHorzRound, aRect.Top() + nVertRound );
+ const Point aTR( aRect.Right() - nHorzRound, aRect.Top() + nVertRound );
+ const Point aBR( aRect.Right() - nHorzRound, aRect.Bottom() - nVertRound );
+ const Point aBL( aRect.Left() + nHorzRound, aRect.Bottom() - nVertRound );
+ tools::Polygon aEllipsePoly( Point(), nHorzRound, nVertRound );
+ sal_uInt16 i, nEnd, nSize4 = aEllipsePoly.GetSize() >> 2;
+
+ ImplInitSize(aEllipsePoly.GetSize() + 1);
+
+ const Point* pSrcAry = aEllipsePoly.GetConstPointAry();
+ Point* pDstAry = mxPointAry.get();
+
+ for( i = 0, nEnd = nSize4; i < nEnd; i++ )
+ pDstAry[ i ] = pSrcAry[ i ] + aTR;
+
+ for( nEnd = nEnd + nSize4; i < nEnd; i++ )
+ pDstAry[ i ] = pSrcAry[ i ] + aTL;
+
+ for( nEnd = nEnd + nSize4; i < nEnd; i++ )
+ pDstAry[ i ] = pSrcAry[ i ] + aBL;
+
+ for( nEnd = nEnd + nSize4; i < nEnd; i++ )
+ pDstAry[ i ] = pSrcAry[ i ] + aBR;
+
+ pDstAry[ nEnd ] = pDstAry[ 0 ];
+ }
+ }
+ else
+ mnPoints = 0;
+}
+
+ImplPolygon::ImplPolygon( const Point& rCenter, tools::Long nRadX, tools::Long nRadY )
+{
+ if( nRadX && nRadY )
+ {
+ sal_uInt16 nPoints;
+ // Compute default (depends on size)
+ tools::Long nRadXY;
+ const bool bOverflow = o3tl::checked_multiply(nRadX, nRadY, nRadXY);
+ if (!bOverflow)
+ {
+ nPoints = std::clamp(
+ ( M_PI * ( 1.5 * ( nRadX + nRadY ) -
+ sqrt( static_cast<double>(std::abs(nRadXY)) ) ) ),
+ 32.0, 256.0 );
+ }
+ else
+ {
+ nPoints = 256;
+ }
+
+ if( ( nRadX > 32 ) && ( nRadY > 32 ) && ( nRadX + nRadY ) < 8192 )
+ nPoints >>= 1;
+
+ // Ceil number of points until divisible by four
+ nPoints = (nPoints + 3) & ~3;
+ ImplInitSize(nPoints);
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints2 = nPoints >> 1;
+ sal_uInt16 nPoints4 = nPoints >> 2;
+ double nAngle;
+ double nAngleStep = M_PI_2 / ( nPoints4 - 1 );
+
+ for( i=0, nAngle = 0.0; i < nPoints4; i++, nAngle += nAngleStep )
+ {
+ tools::Long nX = FRound( nRadX * cos( nAngle ) );
+ tools::Long nY = FRound( -nRadY * sin( nAngle ) );
+
+ Point* pPt = &(mxPointAry[i]);
+ pPt->setX( nX + rCenter.X() );
+ pPt->setY( nY + rCenter.Y() );
+ pPt = &(mxPointAry[nPoints2-i-1]);
+ pPt->setX( -nX + rCenter.X() );
+ pPt->setY( nY + rCenter.Y() );
+ pPt = &(mxPointAry[i+nPoints2]);
+ pPt->setX( -nX + rCenter.X() );
+ pPt->setY( -nY + rCenter.Y() );
+ pPt = &(mxPointAry[nPoints-i-1]);
+ pPt->setX( nX + rCenter.X() );
+ pPt->setY( -nY + rCenter.Y() );
+ }
+ }
+ else
+ mnPoints = 0;
+}
+
+ImplPolygon::ImplPolygon(const tools::Rectangle& rBound, const Point& rStart, const Point& rEnd,
+ PolyStyle eStyle, const bool bClockWiseArcDirection)
+{
+ const auto nWidth = rBound.GetWidth();
+ const auto nHeight = rBound.GetHeight();
+
+ if ((nWidth != 0) && (nHeight != 0))
+ {
+ const Point aCenter(rBound.Center());
+ // tdf#142268 Get Top Left corner of rectangle (the rectangle is not always correctly created)
+ const auto aBoundLeft = rBound.Left() < aCenter.X() ? rBound.Left() : rBound.Right();
+ const auto aBoundTop = rBound.Top() < aCenter.Y() ? rBound.Top() : rBound.Bottom();
+ const auto nRadX = o3tl::saturating_sub(aCenter.X(), aBoundLeft);
+ const auto nRadY = o3tl::saturating_sub(aCenter.Y(), aBoundTop);
+ sal_uInt16 nPoints;
+
+ tools::Long nRadXY;
+ const bool bOverflow = o3tl::checked_multiply(nRadX, nRadY, nRadXY);
+ if (!bOverflow)
+ {
+ nPoints = std::clamp(
+ ( M_PI * ( 1.5 * ( nRadX + nRadY ) -
+ sqrt( static_cast<double>(std::abs(nRadXY)) ) ) ),
+ 32.0, 256.0 );
+ }
+ else
+ {
+ nPoints = 256;
+ }
+
+
+ if (nRadX > 32 && nRadY > 32 && o3tl::saturating_add(nRadX, nRadY) < 8192)
+ nPoints >>= 1;
+
+ // compute threshold
+ const double fRadX = nRadX;
+ const double fRadY = nRadY;
+ const double fCenterX = aCenter.X();
+ const double fCenterY = aCenter.Y();
+ double fStart = ImplGetParameter( aCenter, rStart, fRadX, fRadY );
+ double fEnd = ImplGetParameter( aCenter, rEnd, fRadX, fRadY );
+ double fDiff = fEnd - fStart;
+ double fStep;
+ sal_uInt16 nStart;
+ sal_uInt16 nEnd;
+
+ if (bClockWiseArcDirection == false)
+ {
+ // #i73608# If startPoint is equal to endPoint, then draw full circle instead of nothing (as Metafiles spec)
+ if (fDiff <= 0.)
+ fDiff += 2. * M_PI;
+ }
+ else
+ {
+ fDiff = (2. * M_PI) - fDiff;
+ if (fDiff > 2. * M_PI)
+ fDiff -= 2. * M_PI;
+ }
+
+ // Proportionally shrink number of points( fDiff / (2PI) );
+ nPoints = std::max(static_cast<sal_uInt16>((fDiff / (2. * M_PI)) * nPoints), sal_uInt16(16));
+ fStep = fDiff / (nPoints - 1);
+ if (bClockWiseArcDirection == true)
+ fStep = -fStep;
+
+ if (PolyStyle::Pie == eStyle)
+ {
+ const Point aCenter2(FRound(fCenterX), FRound(fCenterY));
+
+ nStart = 1;
+ nEnd = nPoints + 1;
+ ImplInitSize(nPoints + 2);
+ mxPointAry[0] = aCenter2;
+ mxPointAry[nEnd] = aCenter2;
+ }
+ else
+ {
+ ImplInitSize( ( PolyStyle::Chord == eStyle ) ? ( nPoints + 1 ) : nPoints );
+ nStart = 0;
+ nEnd = nPoints;
+ }
+
+ for(; nStart < nEnd; nStart++, fStart += fStep )
+ {
+ Point& rPt = mxPointAry[nStart];
+
+ rPt.setX( FRound( fCenterX + fRadX * cos( fStart ) ) );
+ rPt.setY( FRound( fCenterY - fRadY * sin( fStart ) ) );
+ }
+
+ if( PolyStyle::Chord == eStyle )
+ mxPointAry[nPoints] = mxPointAry[0];
+ }
+ else
+ mnPoints = 0;
+}
+
+ImplPolygon::ImplPolygon( const Point& rBezPt1, const Point& rCtrlPt1,
+ const Point& rBezPt2, const Point& rCtrlPt2, sal_uInt16 nPoints )
+{
+ nPoints = ( 0 == nPoints ) ? 25 : ( ( nPoints < 2 ) ? 2 : nPoints );
+
+ const double fInc = 1.0 / ( nPoints - 1 );
+ double fK_1 = 0.0, fK1_1 = 1.0;
+ double fK_2, fK_3, fK1_2, fK1_3;
+ const double fX0 = rBezPt1.X();
+ const double fY0 = rBezPt1.Y();
+ const double fX1 = 3.0 * rCtrlPt1.X();
+ const double fY1 = 3.0 * rCtrlPt1.Y();
+ const double fX2 = 3.0 * rCtrlPt2.X();
+ const double fY2 = 3.0 * rCtrlPt2.Y();
+ const double fX3 = rBezPt2.X();
+ const double fY3 = rBezPt2.Y();
+
+ ImplInitSize(nPoints);
+
+ for( sal_uInt16 i = 0; i < nPoints; i++, fK_1 += fInc, fK1_1 -= fInc )
+ {
+ Point& rPt = mxPointAry[i];
+
+ fK_2 = fK_1;
+ fK_2 *= fK_1;
+ fK_3 = fK_2;
+ fK_3 *= fK_1;
+ fK1_2 = fK1_1;
+ fK1_2 *= fK1_1;
+ fK1_3 = fK1_2;
+ fK1_3 *= fK1_1;
+ double fK12 = fK_1 * fK1_2;
+ double fK21 = fK_2 * fK1_1;
+
+ rPt.setX( FRound( fK1_3 * fX0 + fK12 * fX1 + fK21 * fX2 + fK_3 * fX3 ) );
+ rPt.setY( FRound( fK1_3 * fY0 + fK12 * fY1 + fK21 * fY2 + fK_3 * fY3 ) );
+ }
+}
+
+// constructor to convert from basegfx::B2DPolygon
+// #i76891# Needed to change from adding all control points (even for unused
+// edges) and creating a fixed-size Polygon in the first run to creating the
+// minimal Polygon. This requires a temporary Point- and Flag-Array for curves
+// and a memcopy at ImplPolygon creation, but contains no zero-controlpoints
+// for straight edges.
+ImplPolygon::ImplPolygon(const basegfx::B2DPolygon& rPolygon)
+ : mnPoints(0)
+{
+ const bool bCurve(rPolygon.areControlPointsUsed());
+ const bool bClosed(rPolygon.isClosed());
+ sal_uInt32 nB2DLocalCount(rPolygon.count());
+
+ if(bCurve)
+ {
+ // #127979# Reduce source point count hard to the limit of the tools Polygon
+ if(nB2DLocalCount > ((0x0000ffff / 3) - 1))
+ {
+ OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)");
+ nB2DLocalCount = ((0x0000ffff / 3) - 1);
+ }
+
+ // calculate target point count
+ const sal_uInt32 nLoopCount(bClosed ? nB2DLocalCount : (nB2DLocalCount ? nB2DLocalCount - 1 : 0 ));
+
+ if(nLoopCount)
+ {
+ // calculate maximum array size and allocate; prepare insert index
+ const sal_uInt32 nMaxTargetCount((nLoopCount * 3) + 1);
+ ImplInitSize(static_cast< sal_uInt16 >(nMaxTargetCount), true);
+
+ // prepare insert index and current point
+ sal_uInt32 nArrayInsert(0);
+ basegfx::B2DCubicBezier aBezier;
+ aBezier.setStartPoint(rPolygon.getB2DPoint(0));
+
+ for(sal_uInt32 a(0); a < nLoopCount; a++)
+ {
+ // add current point (always) and remember StartPointIndex for evtl. later corrections
+ const Point aStartPoint(FRound(aBezier.getStartPoint().getX()), FRound(aBezier.getStartPoint().getY()));
+ const sal_uInt32 nStartPointIndex(nArrayInsert);
+ mxPointAry[nStartPointIndex] = aStartPoint;
+ mxFlagAry[nStartPointIndex] = PolyFlags::Normal;
+ nArrayInsert++;
+
+ // prepare next segment
+ const sal_uInt32 nNextIndex((a + 1) % nB2DLocalCount);
+ aBezier.setEndPoint(rPolygon.getB2DPoint(nNextIndex));
+ aBezier.setControlPointA(rPolygon.getNextControlPoint(a));
+ aBezier.setControlPointB(rPolygon.getPrevControlPoint(nNextIndex));
+
+ if(aBezier.isBezier())
+ {
+ // if one is used, add always two control points due to the old schema
+ mxPointAry[nArrayInsert] = Point(FRound(aBezier.getControlPointA().getX()), FRound(aBezier.getControlPointA().getY()));
+ mxFlagAry[nArrayInsert] = PolyFlags::Control;
+ nArrayInsert++;
+
+ mxPointAry[nArrayInsert] = Point(FRound(aBezier.getControlPointB().getX()), FRound(aBezier.getControlPointB().getY()));
+ mxFlagAry[nArrayInsert] = PolyFlags::Control;
+ nArrayInsert++;
+ }
+
+ // test continuity with previous control point to set flag value
+ if(aBezier.getControlPointA() != aBezier.getStartPoint() && (bClosed || a))
+ {
+ const basegfx::B2VectorContinuity eCont(rPolygon.getContinuityInPoint(a));
+
+ if(basegfx::B2VectorContinuity::C1 == eCont)
+ {
+ mxFlagAry[nStartPointIndex] = PolyFlags::Smooth;
+ }
+ else if(basegfx::B2VectorContinuity::C2 == eCont)
+ {
+ mxFlagAry[nStartPointIndex] = PolyFlags::Symmetric;
+ }
+ }
+
+ // prepare next polygon step
+ aBezier.setStartPoint(aBezier.getEndPoint());
+ }
+
+ if(bClosed)
+ {
+ // add first point again as closing point due to old definition
+ mxPointAry[nArrayInsert] = mxPointAry[0];
+ mxFlagAry[nArrayInsert] = PolyFlags::Normal;
+ nArrayInsert++;
+ }
+ else
+ {
+ // add last point as closing point
+ const basegfx::B2DPoint aClosingPoint(rPolygon.getB2DPoint(nB2DLocalCount - 1));
+ const Point aEnd(FRound(aClosingPoint.getX()), FRound(aClosingPoint.getY()));
+ mxPointAry[nArrayInsert] = aEnd;
+ mxFlagAry[nArrayInsert] = PolyFlags::Normal;
+ nArrayInsert++;
+ }
+
+ DBG_ASSERT(nArrayInsert <= nMaxTargetCount, "Polygon::Polygon from basegfx::B2DPolygon: wrong max point count estimation (!)");
+
+ if(nArrayInsert != nMaxTargetCount)
+ {
+ ImplSetSize(static_cast< sal_uInt16 >(nArrayInsert));
+ }
+ }
+ }
+ else
+ {
+ // #127979# Reduce source point count hard to the limit of the tools Polygon
+ if(nB2DLocalCount > (0x0000ffff - 1))
+ {
+ OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)");
+ nB2DLocalCount = (0x0000ffff - 1);
+ }
+
+ if(nB2DLocalCount)
+ {
+ // point list creation
+ const sal_uInt32 nTargetCount(nB2DLocalCount + (bClosed ? 1 : 0));
+ ImplInitSize(static_cast< sal_uInt16 >(nTargetCount));
+ sal_uInt16 nIndex(0);
+
+ for(sal_uInt32 a(0); a < nB2DLocalCount; a++)
+ {
+ basegfx::B2DPoint aB2DPoint(rPolygon.getB2DPoint(a));
+ Point aPoint(FRound(aB2DPoint.getX()), FRound(aB2DPoint.getY()));
+ mxPointAry[nIndex++] = aPoint;
+ }
+
+ if(bClosed)
+ {
+ // add first point as closing point
+ mxPointAry[nIndex] = mxPointAry[0];
+ }
+ }
+ }
+}
+
+bool ImplPolygon::operator==( const ImplPolygon& rCandidate) const
+{
+ return mnPoints == rCandidate.mnPoints &&
+ mxFlagAry.get() == rCandidate.mxFlagAry.get() &&
+ mxPointAry.get() == rCandidate.mxPointAry.get();
+}
+
+void ImplPolygon::ImplInitSize(sal_uInt16 nInitSize, bool bFlags)
+{
+ if (nInitSize)
+ {
+ mxPointAry.reset(new Point[nInitSize]);
+ }
+
+ if (bFlags)
+ {
+ mxFlagAry.reset(new PolyFlags[nInitSize]);
+ memset(mxFlagAry.get(), 0, nInitSize);
+ }
+
+ mnPoints = nInitSize;
+}
+
+void ImplPolygon::ImplSetSize( sal_uInt16 nNewSize, bool bResize )
+{
+ if( mnPoints == nNewSize )
+ return;
+
+ std::unique_ptr<Point[]> xNewAry;
+
+ if (nNewSize)
+ {
+ const std::size_t nNewSz(static_cast<std::size_t>(nNewSize)*sizeof(Point));
+ xNewAry.reset(new Point[nNewSize]);
+
+ if ( bResize )
+ {
+ // Copy the old points
+ if ( mnPoints < nNewSize )
+ {
+ // New points are already implicitly initialized to zero
+ const std::size_t nOldSz(mnPoints * sizeof(Point));
+ if (mxPointAry)
+ memcpy(xNewAry.get(), mxPointAry.get(), nOldSz);
+ }
+ else
+ {
+ if (mxPointAry)
+ memcpy(xNewAry.get(), mxPointAry.get(), nNewSz);
+ }
+ }
+ }
+
+ mxPointAry = std::move(xNewAry);
+
+ // take FlagArray into account, if applicable
+ if( mxFlagAry )
+ {
+ std::unique_ptr<PolyFlags[]> xNewFlagAry;
+
+ if( nNewSize )
+ {
+ xNewFlagAry.reset(new PolyFlags[nNewSize]);
+
+ if( bResize )
+ {
+ // copy the old flags
+ if ( mnPoints < nNewSize )
+ {
+ // initialize new flags to zero
+ memset(xNewFlagAry.get() + mnPoints, 0, nNewSize-mnPoints);
+ memcpy(xNewFlagAry.get(), mxFlagAry.get(), mnPoints);
+ }
+ else
+ memcpy(xNewFlagAry.get(), mxFlagAry.get(), nNewSize);
+ }
+ }
+
+ mxFlagAry = std::move(xNewFlagAry);
+ }
+
+ mnPoints = nNewSize;
+}
+
+bool ImplPolygon::ImplSplit( sal_uInt16 nPos, sal_uInt16 nSpace, ImplPolygon const * pInitPoly )
+{
+ //Can't fit this in :-(, throw ?
+ if (mnPoints + nSpace > USHRT_MAX)
+ {
+ SAL_WARN("tools", "Polygon needs " << mnPoints + nSpace << " points, but only " << USHRT_MAX << " possible");
+ return false;
+ }
+
+ const sal_uInt16 nNewSize = mnPoints + nSpace;
+ const std::size_t nSpaceSize = static_cast<std::size_t>(nSpace) * sizeof(Point);
+
+ if( nPos >= mnPoints )
+ {
+ // Append at the back
+ nPos = mnPoints;
+ ImplSetSize( nNewSize );
+
+ if( pInitPoly )
+ {
+ memcpy(mxPointAry.get() + nPos, pInitPoly->mxPointAry.get(), nSpaceSize);
+
+ if (pInitPoly->mxFlagAry)
+ memcpy(mxFlagAry.get() + nPos, pInitPoly->mxFlagAry.get(), nSpace);
+ }
+ }
+ else
+ {
+ const sal_uInt16 nSecPos = nPos + nSpace;
+ const sal_uInt16 nRest = mnPoints - nPos;
+
+ std::unique_ptr<Point[]> xNewAry(new Point[nNewSize]);
+ memcpy(xNewAry.get(), mxPointAry.get(), nPos * sizeof(Point));
+
+ if( pInitPoly )
+ memcpy(xNewAry.get() + nPos, pInitPoly->mxPointAry.get(), nSpaceSize);
+
+ memcpy(xNewAry.get() + nSecPos, mxPointAry.get() + nPos, nRest * sizeof(Point));
+ mxPointAry = std::move(xNewAry);
+
+ // consider FlagArray
+ if (mxFlagAry)
+ {
+ std::unique_ptr<PolyFlags[]> xNewFlagAry(new PolyFlags[nNewSize]);
+
+ memcpy(xNewFlagAry.get(), mxFlagAry.get(), nPos);
+
+ if (pInitPoly && pInitPoly->mxFlagAry)
+ memcpy(xNewFlagAry.get() + nPos, pInitPoly->mxFlagAry.get(), nSpace);
+ else
+ memset(xNewFlagAry.get() + nPos, 0, nSpace);
+
+ memcpy(xNewFlagAry.get() + nSecPos, mxFlagAry.get() + nPos, nRest);
+ mxFlagAry = std::move(xNewFlagAry);
+ }
+
+ mnPoints = nNewSize;
+ }
+
+ return true;
+}
+
+void ImplPolygon::ImplCreateFlagArray()
+{
+ if (!mxFlagAry)
+ {
+ mxFlagAry.reset(new PolyFlags[mnPoints]);
+ memset(mxFlagAry.get(), 0, mnPoints);
+ }
+}
+
+namespace {
+
+class ImplPointFilter
+{
+public:
+ virtual void LastPoint() = 0;
+ virtual void Input( const Point& rPoint ) = 0;
+
+protected:
+ ~ImplPointFilter() {}
+};
+
+class ImplPolygonPointFilter : public ImplPointFilter
+{
+ ImplPolygon maPoly;
+ sal_uInt16 mnSize;
+public:
+ explicit ImplPolygonPointFilter(sal_uInt16 nDestSize)
+ : maPoly(nDestSize)
+ , mnSize(0)
+ {
+ }
+
+ virtual ~ImplPolygonPointFilter()
+ {
+ }
+
+ virtual void LastPoint() override;
+ virtual void Input( const Point& rPoint ) override;
+
+ ImplPolygon& get() { return maPoly; }
+};
+
+}
+
+void ImplPolygonPointFilter::Input( const Point& rPoint )
+{
+ if ( !mnSize || (rPoint != maPoly.mxPointAry[mnSize-1]) )
+ {
+ mnSize++;
+ if ( mnSize > maPoly.mnPoints )
+ maPoly.ImplSetSize( mnSize );
+ maPoly.mxPointAry[mnSize-1] = rPoint;
+ }
+}
+
+void ImplPolygonPointFilter::LastPoint()
+{
+ if ( mnSize < maPoly.mnPoints )
+ maPoly.ImplSetSize( mnSize );
+};
+
+namespace {
+
+class ImplEdgePointFilter : public ImplPointFilter
+{
+ Point maFirstPoint;
+ Point maLastPoint;
+ ImplPointFilter& mrNextFilter;
+ const tools::Long mnLow;
+ const tools::Long mnHigh;
+ const int mnEdge;
+ int mnLastOutside;
+ bool mbFirst;
+
+public:
+ ImplEdgePointFilter( int nEdge, tools::Long nLow, tools::Long nHigh,
+ ImplPointFilter& rNextFilter ) :
+ mrNextFilter( rNextFilter ),
+ mnLow( nLow ),
+ mnHigh( nHigh ),
+ mnEdge( nEdge ),
+ mnLastOutside( 0 ),
+ mbFirst( true )
+ {
+ }
+
+ virtual ~ImplEdgePointFilter() {}
+
+ Point EdgeSection( const Point& rPoint, int nEdge ) const;
+ int VisibleSide( const Point& rPoint ) const;
+ bool IsPolygon() const
+ { return maFirstPoint == maLastPoint; }
+
+ virtual void Input( const Point& rPoint ) override;
+ virtual void LastPoint() override;
+};
+
+}
+
+inline int ImplEdgePointFilter::VisibleSide( const Point& rPoint ) const
+{
+ if ( mnEdge & EDGE_HORZ )
+ {
+ return rPoint.X() < mnLow ? EDGE_LEFT :
+ rPoint.X() > mnHigh ? EDGE_RIGHT : 0;
+ }
+ else
+ {
+ return rPoint.Y() < mnLow ? EDGE_TOP :
+ rPoint.Y() > mnHigh ? EDGE_BOTTOM : 0;
+ }
+}
+
+Point ImplEdgePointFilter::EdgeSection( const Point& rPoint, int nEdge ) const
+{
+ tools::Long lx = maLastPoint.X();
+ tools::Long ly = maLastPoint.Y();
+ tools::Long md = rPoint.X() - lx;
+ tools::Long mn = rPoint.Y() - ly;
+ tools::Long nNewX;
+ tools::Long nNewY;
+
+ if ( nEdge & EDGE_VERT )
+ {
+ nNewY = (nEdge == EDGE_TOP) ? mnLow : mnHigh;
+ tools::Long dy = nNewY - ly;
+ if ( !md )
+ nNewX = lx;
+ else if ( (LONG_MAX / std::abs(md)) >= std::abs(dy) )
+ nNewX = (dy * md) / mn + lx;
+ else
+ {
+ BigInt ady = dy;
+ ady *= md;
+ if( ady.IsNeg() )
+ if( mn < 0 )
+ ady += mn/2;
+ else
+ ady -= (mn-1)/2;
+ else
+ if( mn < 0 )
+ ady -= (mn+1)/2;
+ else
+ ady += mn/2;
+ ady /= mn;
+ nNewX = static_cast<tools::Long>(ady) + lx;
+ }
+ }
+ else
+ {
+ nNewX = (nEdge == EDGE_LEFT) ? mnLow : mnHigh;
+ tools::Long dx = nNewX - lx;
+ if ( !mn )
+ nNewY = ly;
+ else if ( (LONG_MAX / std::abs(mn)) >= std::abs(dx) )
+ nNewY = (dx * mn) / md + ly;
+ else
+ {
+ BigInt adx = dx;
+ adx *= mn;
+ if( adx.IsNeg() )
+ if( md < 0 )
+ adx += md/2;
+ else
+ adx -= (md-1)/2;
+ else
+ if( md < 0 )
+ adx -= (md+1)/2;
+ else
+ adx += md/2;
+ adx /= md;
+ nNewY = static_cast<tools::Long>(adx) + ly;
+ }
+ }
+
+ return Point( nNewX, nNewY );
+}
+
+void ImplEdgePointFilter::Input( const Point& rPoint )
+{
+ int nOutside = VisibleSide( rPoint );
+
+ if ( mbFirst )
+ {
+ maFirstPoint = rPoint;
+ mbFirst = false;
+ if ( !nOutside )
+ mrNextFilter.Input( rPoint );
+ }
+ else if ( rPoint == maLastPoint )
+ return;
+ else if ( !nOutside )
+ {
+ if ( mnLastOutside )
+ mrNextFilter.Input( EdgeSection( rPoint, mnLastOutside ) );
+ mrNextFilter.Input( rPoint );
+ }
+ else if ( !mnLastOutside )
+ mrNextFilter.Input( EdgeSection( rPoint, nOutside ) );
+ else if ( nOutside != mnLastOutside )
+ {
+ mrNextFilter.Input( EdgeSection( rPoint, mnLastOutside ) );
+ mrNextFilter.Input( EdgeSection( rPoint, nOutside ) );
+ }
+
+ maLastPoint = rPoint;
+ mnLastOutside = nOutside;
+}
+
+void ImplEdgePointFilter::LastPoint()
+{
+ if ( !mbFirst )
+ {
+ int nOutside = VisibleSide( maFirstPoint );
+
+ if ( nOutside != mnLastOutside )
+ Input( maFirstPoint );
+ mrNextFilter.LastPoint();
+ }
+}
+
+namespace tools
+{
+
+tools::Polygon Polygon::SubdivideBezier( const tools::Polygon& rPoly )
+{
+ tools::Polygon aPoly;
+
+ // #100127# Use adaptive subdivide instead of fixed 25 segments
+ rPoly.AdaptiveSubdivide( aPoly );
+
+ return aPoly;
+}
+
+Polygon::Polygon() : mpImplPolygon(ImplPolygon())
+{
+}
+
+Polygon::Polygon( sal_uInt16 nSize ) : mpImplPolygon(ImplPolygon(nSize))
+{
+}
+
+Polygon::Polygon( sal_uInt16 nPoints, const Point* pPtAry, const PolyFlags* pFlagAry ) : mpImplPolygon(ImplPolygon(nPoints, pPtAry, pFlagAry))
+{
+}
+
+Polygon::Polygon( const tools::Polygon& rPoly ) : mpImplPolygon(rPoly.mpImplPolygon)
+{
+}
+
+Polygon::Polygon( tools::Polygon&& rPoly) noexcept
+ : mpImplPolygon(std::move(rPoly.mpImplPolygon))
+{
+}
+
+Polygon::Polygon( const tools::Rectangle& rRect ) : mpImplPolygon(ImplPolygon(rRect))
+{
+}
+
+Polygon::Polygon( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
+ : mpImplPolygon(ImplPolygon(rRect, nHorzRound, nVertRound))
+{
+}
+
+Polygon::Polygon( const Point& rCenter, tools::Long nRadX, tools::Long nRadY )
+ : mpImplPolygon(ImplPolygon(rCenter, nRadX, nRadY))
+{
+}
+
+Polygon::Polygon(const tools::Rectangle& rBound, const Point& rStart, const Point& rEnd,
+ PolyStyle eStyle, const bool bClockWiseArcDirection)
+ : mpImplPolygon(ImplPolygon(rBound, rStart, rEnd, eStyle, bClockWiseArcDirection))
+{
+}
+
+Polygon::Polygon( const Point& rBezPt1, const Point& rCtrlPt1,
+ const Point& rBezPt2, const Point& rCtrlPt2,
+ sal_uInt16 nPoints ) : mpImplPolygon(ImplPolygon(rBezPt1, rCtrlPt1, rBezPt2, rCtrlPt2, nPoints))
+{
+}
+
+Polygon::~Polygon()
+{
+}
+
+Point * Polygon::GetPointAry()
+{
+ return mpImplPolygon->mxPointAry.get();
+}
+
+const Point* Polygon::GetConstPointAry() const
+{
+ return mpImplPolygon->mxPointAry.get();
+}
+
+const PolyFlags* Polygon::GetConstFlagAry() const
+{
+ return mpImplPolygon->mxFlagAry.get();
+}
+
+void Polygon::SetPoint( const Point& rPt, sal_uInt16 nPos )
+{
+ DBG_ASSERT( nPos < mpImplPolygon->mnPoints,
+ "Polygon::SetPoint(): nPos >= nPoints" );
+
+ mpImplPolygon->mxPointAry[nPos] = rPt;
+}
+
+void Polygon::SetFlags( sal_uInt16 nPos, PolyFlags eFlags )
+{
+ DBG_ASSERT( nPos < mpImplPolygon->mnPoints,
+ "Polygon::SetFlags(): nPos >= nPoints" );
+
+ // we do only want to create the flag array if there
+ // is at least one flag different to PolyFlags::Normal
+ if ( eFlags != PolyFlags::Normal )
+ {
+ mpImplPolygon->ImplCreateFlagArray();
+ mpImplPolygon->mxFlagAry[ nPos ] = eFlags;
+ }
+}
+
+const Point& Polygon::GetPoint( sal_uInt16 nPos ) const
+{
+ DBG_ASSERT( nPos < mpImplPolygon->mnPoints,
+ "Polygon::GetPoint(): nPos >= nPoints" );
+
+ return mpImplPolygon->mxPointAry[nPos];
+}
+
+PolyFlags Polygon::GetFlags( sal_uInt16 nPos ) const
+{
+ DBG_ASSERT( nPos < mpImplPolygon->mnPoints,
+ "Polygon::GetFlags(): nPos >= nPoints" );
+ return mpImplPolygon->mxFlagAry
+ ? mpImplPolygon->mxFlagAry[ nPos ]
+ : PolyFlags::Normal;
+}
+
+bool Polygon::HasFlags() const
+{
+ return bool(mpImplPolygon->mxFlagAry);
+}
+
+bool Polygon::IsRect() const
+{
+ bool bIsRect = false;
+ if (!mpImplPolygon->mxFlagAry)
+ {
+ if ( ( ( mpImplPolygon->mnPoints == 5 ) && ( mpImplPolygon->mxPointAry[ 0 ] == mpImplPolygon->mxPointAry[ 4 ] ) ) ||
+ ( mpImplPolygon->mnPoints == 4 ) )
+ {
+ if ( ( mpImplPolygon->mxPointAry[ 0 ].X() == mpImplPolygon->mxPointAry[ 3 ].X() ) &&
+ ( mpImplPolygon->mxPointAry[ 0 ].Y() == mpImplPolygon->mxPointAry[ 1 ].Y() ) &&
+ ( mpImplPolygon->mxPointAry[ 1 ].X() == mpImplPolygon->mxPointAry[ 2 ].X() ) &&
+ ( mpImplPolygon->mxPointAry[ 2 ].Y() == mpImplPolygon->mxPointAry[ 3 ].Y() ) )
+ bIsRect = true;
+ }
+ }
+ return bIsRect;
+}
+
+void Polygon::SetSize( sal_uInt16 nNewSize )
+{
+ if( nNewSize != mpImplPolygon->mnPoints )
+ {
+ mpImplPolygon->ImplSetSize( nNewSize );
+ }
+}
+
+sal_uInt16 Polygon::GetSize() const
+{
+ return mpImplPolygon->mnPoints;
+}
+
+void Polygon::Clear()
+{
+ mpImplPolygon = ImplType(ImplPolygon());
+}
+
+double Polygon::CalcDistance( sal_uInt16 nP1, sal_uInt16 nP2 ) const
+{
+ DBG_ASSERT( nP1 < mpImplPolygon->mnPoints,
+ "Polygon::CalcDistance(): nPos1 >= nPoints" );
+ DBG_ASSERT( nP2 < mpImplPolygon->mnPoints,
+ "Polygon::CalcDistance(): nPos2 >= nPoints" );
+
+ const Point& rP1 = mpImplPolygon->mxPointAry[ nP1 ];
+ const Point& rP2 = mpImplPolygon->mxPointAry[ nP2 ];
+ const double fDx = rP2.X() - rP1.X();
+ const double fDy = rP2.Y() - rP1.Y();
+
+ return std::hypot( fDx, fDy );
+}
+
+void Polygon::Optimize( PolyOptimizeFlags nOptimizeFlags )
+{
+ sal_uInt16 nSize = mpImplPolygon->mnPoints;
+
+ if( !(bool(nOptimizeFlags) && nSize) )
+ return;
+
+ if( nOptimizeFlags & PolyOptimizeFlags::EDGES )
+ {
+ const tools::Rectangle aBound( GetBoundRect() );
+ const double fArea = ( aBound.GetWidth() + aBound.GetHeight() ) * 0.5;
+ const sal_uInt16 nPercent = 50;
+
+ Optimize( PolyOptimizeFlags::NO_SAME );
+ ImplReduceEdges( *this, fArea, nPercent );
+ }
+ else if( nOptimizeFlags & PolyOptimizeFlags::NO_SAME )
+ {
+ tools::Polygon aNewPoly;
+ const Point& rFirst = mpImplPolygon->mxPointAry[ 0 ];
+
+ while( nSize && ( mpImplPolygon->mxPointAry[ nSize - 1 ] == rFirst ) )
+ nSize--;
+
+ if( nSize > 1 )
+ {
+ sal_uInt16 nLast = 0, nNewCount = 1;
+
+ aNewPoly.SetSize( nSize );
+ aNewPoly[ 0 ] = rFirst;
+
+ for( sal_uInt16 i = 1; i < nSize; i++ )
+ {
+ if( mpImplPolygon->mxPointAry[ i ] != mpImplPolygon->mxPointAry[ nLast ])
+ {
+ nLast = i;
+ aNewPoly[ nNewCount++ ] = mpImplPolygon->mxPointAry[ i ];
+ }
+ }
+
+ if( nNewCount == 1 )
+ aNewPoly.Clear();
+ else
+ aNewPoly.SetSize( nNewCount );
+ }
+
+ *this = aNewPoly;
+ }
+
+ nSize = mpImplPolygon->mnPoints;
+
+ if( nSize > 1 )
+ {
+ if( ( nOptimizeFlags & PolyOptimizeFlags::CLOSE ) &&
+ ( mpImplPolygon->mxPointAry[ 0 ] != mpImplPolygon->mxPointAry[ nSize - 1 ] ) )
+ {
+ SetSize( mpImplPolygon->mnPoints + 1 );
+ mpImplPolygon->mxPointAry[ mpImplPolygon->mnPoints - 1 ] = mpImplPolygon->mxPointAry[ 0 ];
+ }
+ }
+}
+
+
+/** Recursively subdivide cubic bezier curve via deCasteljau.
+
+ @param rPoints
+ Output vector, where the subdivided polylines are written to.
+
+ @param d
+ Squared difference of curve to a straight line
+
+ @param P*
+ Exactly four points, interpreted as support and control points of
+ a cubic bezier curve. Must be in device coordinates, since stop
+ criterion is based on the following assumption: the device has a
+ finite resolution, it is thus sufficient to stop subdivision if the
+ curve does not deviate more than one pixel from a straight line.
+
+*/
+static void ImplAdaptiveSubdivide( std::vector<Point>& rPoints,
+ const double old_d2,
+ int recursionDepth,
+ const double d2,
+ const double P1x, const double P1y,
+ const double P2x, const double P2y,
+ const double P3x, const double P3y,
+ const double P4x, const double P4y )
+{
+ // Hard limit on recursion depth, empiric number.
+ enum {maxRecursionDepth=128};
+
+ // Perform bezier flatness test (lecture notes from R. Schaback,
+ // Mathematics of Computer-Aided Design, Uni Goettingen, 2000)
+
+ // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)||
+ // 0<=j<=n
+
+ // What is calculated here is an upper bound to the distance from
+ // a line through b_0 and b_3 (P1 and P4 in our notation) and the
+ // curve. We can drop 0 and n from the running indices, since the
+ // argument of max becomes zero for those cases.
+ const double fJ1x( P2x - P1x - 1.0/3.0*(P4x - P1x) );
+ const double fJ1y( P2y - P1y - 1.0/3.0*(P4y - P1y) );
+ const double fJ2x( P3x - P1x - 2.0/3.0*(P4x - P1x) );
+ const double fJ2y( P3y - P1y - 2.0/3.0*(P4y - P1y) );
+ const double distance2( ::std::max( fJ1x*fJ1x + fJ1y*fJ1y,
+ fJ2x*fJ2x + fJ2y*fJ2y) );
+
+ // stop if error measure does not improve anymore. This is a
+ // safety guard against floating point inaccuracies.
+ // stop at recursion level 128. This is a safety guard against
+ // floating point inaccuracies.
+ // stop if distance from line is guaranteed to be bounded by d
+ if( old_d2 > d2 &&
+ recursionDepth < maxRecursionDepth &&
+ distance2 >= d2 &&
+ rPoints.size() < SAL_MAX_UINT16 )
+ {
+ // deCasteljau bezier arc, split at t=0.5
+ // Foley/vanDam, p. 508
+ const double L1x( P1x ), L1y( P1y );
+ const double L2x( (P1x + P2x)*0.5 ), L2y( (P1y + P2y)*0.5 );
+ const double Hx ( (P2x + P3x)*0.5 ), Hy ( (P2y + P3y)*0.5 );
+ const double L3x( (L2x + Hx)*0.5 ), L3y( (L2y + Hy)*0.5 );
+ const double R4x( P4x ), R4y( P4y );
+ const double R3x( (P3x + P4x)*0.5 ), R3y( (P3y + P4y)*0.5 );
+ const double R2x( (Hx + R3x)*0.5 ), R2y( (Hy + R3y)*0.5 );
+ const double R1x( (L3x + R2x)*0.5 ), R1y( (L3y + R2y)*0.5 );
+ const double L4x( R1x ), L4y( R1y );
+
+ // subdivide further
+ ++recursionDepth;
+ ImplAdaptiveSubdivide(rPoints, distance2, recursionDepth, d2, L1x, L1y, L2x, L2y, L3x, L3y, L4x, L4y);
+ ImplAdaptiveSubdivide(rPoints, distance2, recursionDepth, d2, R1x, R1y, R2x, R2y, R3x, R3y, R4x, R4y);
+ }
+ else
+ {
+ // requested resolution reached.
+ // Add end points to output iterator.
+ // order is preserved, since this is so to say depth first traversal.
+ rPoints.push_back(Point(FRound(P1x), FRound(P1y)));
+ }
+}
+
+void Polygon::AdaptiveSubdivide( Polygon& rResult, const double d ) const
+{
+ if (!mpImplPolygon->mxFlagAry)
+ {
+ rResult = *this;
+ }
+ else
+ {
+ sal_uInt16 i;
+ sal_uInt16 nPts( GetSize() );
+ ::std::vector< Point > aPoints;
+ aPoints.reserve( nPts );
+
+ for(i=0; i<nPts;)
+ {
+ if( ( i + 3 ) < nPts )
+ {
+ PolyFlags P1( mpImplPolygon->mxFlagAry[ i ] );
+ PolyFlags P4( mpImplPolygon->mxFlagAry[ i + 3 ] );
+
+ if( ( PolyFlags::Normal == P1 || PolyFlags::Smooth == P1 || PolyFlags::Symmetric == P1 ) &&
+ ( PolyFlags::Control == mpImplPolygon->mxFlagAry[ i + 1 ] ) &&
+ ( PolyFlags::Control == mpImplPolygon->mxFlagAry[ i + 2 ] ) &&
+ ( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) )
+ {
+ ImplAdaptiveSubdivide( aPoints, d*d+1.0, 0, d*d,
+ mpImplPolygon->mxPointAry[ i ].X(), mpImplPolygon->mxPointAry[ i ].Y(),
+ mpImplPolygon->mxPointAry[ i+1 ].X(), mpImplPolygon->mxPointAry[ i+1 ].Y(),
+ mpImplPolygon->mxPointAry[ i+2 ].X(), mpImplPolygon->mxPointAry[ i+2 ].Y(),
+ mpImplPolygon->mxPointAry[ i+3 ].X(), mpImplPolygon->mxPointAry[ i+3 ].Y() );
+ i += 3;
+ continue;
+ }
+ }
+
+ aPoints.push_back(mpImplPolygon->mxPointAry[i++]);
+
+ if (aPoints.size() >= SAL_MAX_UINT16)
+ {
+ OSL_ENSURE(aPoints.size() < SAL_MAX_UINT16,
+ "Polygon::AdaptiveSubdivision created polygon too many points;"
+ " using original polygon instead");
+
+ // The resulting polygon can not hold all the points
+ // that we have created so far. Stop the subdivision
+ // and return a copy of the unmodified polygon.
+ rResult = *this;
+ return;
+ }
+ }
+
+ // fill result polygon
+ rResult = tools::Polygon( static_cast<sal_uInt16>(aPoints.size()) ); // ensure sufficient size for copy
+ ::std::copy(aPoints.begin(), aPoints.end(), rResult.mpImplPolygon->mxPointAry.get());
+ }
+}
+
+namespace {
+
+class Vector2D
+{
+private:
+ double mfX;
+ double mfY;
+public:
+ explicit Vector2D( const Point& rPoint ) : mfX( rPoint.X() ), mfY( rPoint.Y() ) {};
+ double GetLength() const { return hypot( mfX, mfY ); }
+ Vector2D& operator-=( const Vector2D& rVec ) { mfX -= rVec.mfX; mfY -= rVec.mfY; return *this; }
+ double Scalar( const Vector2D& rVec ) const { return mfX * rVec.mfX + mfY * rVec.mfY ; }
+ Vector2D& Normalize();
+ bool IsPositive( Vector2D const & rVec ) const { return ( mfX * rVec.mfY - mfY * rVec.mfX ) >= 0.0; }
+ bool IsNegative( Vector2D const & rVec ) const { return !IsPositive( rVec ); }
+};
+
+}
+
+Vector2D& Vector2D::Normalize()
+{
+ double fLen = Scalar( *this );
+
+ if( ( fLen != 0.0 ) && ( fLen != 1.0 ) )
+ {
+ fLen = sqrt( fLen );
+ if( fLen != 0.0 )
+ {
+ mfX /= fLen;
+ mfY /= fLen;
+ }
+ }
+
+ return *this;
+}
+
+void Polygon::ImplReduceEdges( tools::Polygon& rPoly, const double& rArea, sal_uInt16 nPercent )
+{
+ const double fBound = 2000.0 * ( 100 - nPercent ) * 0.01;
+ sal_uInt16 nNumNoChange = 0,
+ nNumRuns = 0;
+
+ while( nNumNoChange < 2 )
+ {
+ sal_uInt16 nPntCnt = rPoly.GetSize(), nNewPos = 0;
+ tools::Polygon aNewPoly( nPntCnt );
+ bool bChangeInThisRun = false;
+
+ for( sal_uInt16 n = 0; n < nPntCnt; n++ )
+ {
+ bool bDeletePoint = false;
+
+ if( ( n + nNumRuns ) % 2 )
+ {
+ sal_uInt16 nIndPrev = !n ? nPntCnt - 1 : n - 1;
+ sal_uInt16 nIndPrevPrev = !nIndPrev ? nPntCnt - 1 : nIndPrev - 1;
+ sal_uInt16 nIndNext = ( n == nPntCnt-1 ) ? 0 : n + 1;
+ sal_uInt16 nIndNextNext = ( nIndNext == nPntCnt - 1 ) ? 0 : nIndNext + 1;
+ Vector2D aVec1( rPoly[ nIndPrev ] ); aVec1 -= Vector2D(rPoly[ nIndPrevPrev ]);
+ Vector2D aVec2( rPoly[ n ] ); aVec2 -= Vector2D(rPoly[ nIndPrev ]);
+ Vector2D aVec3( rPoly[ nIndNext ] ); aVec3 -= Vector2D(rPoly[ n ]);
+ Vector2D aVec4( rPoly[ nIndNextNext ] ); aVec4 -= Vector2D(rPoly[ nIndNext ]);
+ double fDist1 = aVec1.GetLength(), fDist2 = aVec2.GetLength();
+ double fDist3 = aVec3.GetLength(), fDist4 = aVec4.GetLength();
+ double fTurnB = aVec2.Normalize().Scalar( aVec3.Normalize() );
+
+ if( fabs( fTurnB ) < ( 1.0 + SMALL_DVALUE ) && fabs( fTurnB ) > ( 1.0 - SMALL_DVALUE ) )
+ bDeletePoint = true;
+ else
+ {
+ Vector2D aVecB( rPoly[ nIndNext ] );
+ aVecB -= Vector2D(rPoly[ nIndPrev ] );
+ double fDistB = aVecB.GetLength();
+ double fLenWithB = fDist2 + fDist3;
+ double fLenFact = ( fDistB != 0.0 ) ? fLenWithB / fDistB : 1.0;
+ double fTurnPrev = aVec1.Normalize().Scalar( aVec2 );
+ double fTurnNext = aVec3.Scalar( aVec4.Normalize() );
+ double fGradPrev, fGradB, fGradNext;
+
+ if( fabs( fTurnPrev ) < ( 1.0 + SMALL_DVALUE ) && fabs( fTurnPrev ) > ( 1.0 - SMALL_DVALUE ) )
+ fGradPrev = 0.0;
+ else
+ fGradPrev = basegfx::rad2deg(acos(fTurnPrev)) * (aVec1.IsNegative(aVec2) ? -1 : 1);
+
+ fGradB = basegfx::rad2deg(acos(fTurnB)) * (aVec2.IsNegative(aVec3) ? -1 : 1);
+
+ if( fabs( fTurnNext ) < ( 1.0 + SMALL_DVALUE ) && fabs( fTurnNext ) > ( 1.0 - SMALL_DVALUE ) )
+ fGradNext = 0.0;
+ else
+ fGradNext = basegfx::rad2deg(acos(fTurnNext)) * (aVec3.IsNegative(aVec4) ? -1 : 1);
+
+ if( ( fGradPrev > 0.0 && fGradB < 0.0 && fGradNext > 0.0 ) ||
+ ( fGradPrev < 0.0 && fGradB > 0.0 && fGradNext < 0.0 ) )
+ {
+ if( ( fLenFact < ( FSQRT2 + SMALL_DVALUE ) ) &&
+ ( ( ( fDist1 + fDist4 ) / ( fDist2 + fDist3 ) ) * 2000.0 ) > fBound )
+ {
+ bDeletePoint = true;
+ }
+ }
+ else
+ {
+ double fRelLen = 1.0 - sqrt( fDistB / rArea );
+
+ if( fRelLen < 0.0 )
+ fRelLen = 0.0;
+ else if( fRelLen > 1.0 )
+ fRelLen = 1.0;
+
+ if( ( std::round( ( fLenFact - 1.0 ) * 1000000.0 ) < fBound ) &&
+ ( fabs( fGradB ) <= ( fRelLen * fBound * 0.01 ) ) )
+ {
+ bDeletePoint = true;
+ }
+ }
+ }
+ }
+
+ if( !bDeletePoint )
+ aNewPoly[ nNewPos++ ] = rPoly[ n ];
+ else
+ bChangeInThisRun = true;
+ }
+
+ if( bChangeInThisRun && nNewPos )
+ {
+ aNewPoly.SetSize( nNewPos );
+ rPoly = aNewPoly;
+ nNumNoChange = 0;
+ }
+ else
+ nNumNoChange++;
+
+ nNumRuns++;
+ }
+}
+
+void Polygon::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ // This check is required for DrawEngine
+ if ( !nHorzMove && !nVertMove )
+ return;
+
+ // Move points
+ sal_uInt16 nCount = mpImplPolygon->mnPoints;
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ Point& rPt = mpImplPolygon->mxPointAry[i];
+ rPt.AdjustX(nHorzMove );
+ rPt.AdjustY(nVertMove );
+ }
+}
+
+void Polygon::Translate(const Point& rTrans)
+{
+ for ( sal_uInt16 i = 0, nCount = mpImplPolygon->mnPoints; i < nCount; i++ )
+ mpImplPolygon->mxPointAry[ i ] += rTrans;
+}
+
+void Polygon::Scale( double fScaleX, double fScaleY )
+{
+ for ( sal_uInt16 i = 0, nCount = mpImplPolygon->mnPoints; i < nCount; i++ )
+ {
+ Point& rPnt = mpImplPolygon->mxPointAry[i];
+ rPnt.setX( static_cast<tools::Long>( fScaleX * rPnt.X() ) );
+ rPnt.setY( static_cast<tools::Long>( fScaleY * rPnt.Y() ) );
+ }
+}
+
+void Polygon::Rotate( const Point& rCenter, Degree10 nAngle10 )
+{
+ nAngle10 %= 3600_deg10;
+
+ if( nAngle10 )
+ {
+ const double fAngle = toRadians(nAngle10);
+ Rotate( rCenter, sin( fAngle ), cos( fAngle ) );
+ }
+}
+
+void Polygon::Rotate( const Point& rCenter, double fSin, double fCos )
+{
+ tools::Long nCenterX = rCenter.X();
+ tools::Long nCenterY = rCenter.Y();
+
+ for( sal_uInt16 i = 0, nCount = mpImplPolygon->mnPoints; i < nCount; i++ )
+ {
+ Point& rPt = mpImplPolygon->mxPointAry[ i ];
+
+ const tools::Long nX = rPt.X() - nCenterX;
+ const tools::Long nY = rPt.Y() - nCenterY;
+ rPt.setX( FRound(fCos * nX + fSin * nY + nCenterX) );
+ rPt.setY( FRound(-(fSin * nX - fCos * nY - nCenterY)) );
+ }
+}
+
+void Polygon::Clip( const tools::Rectangle& rRect )
+{
+ // This algorithm is broken for bezier curves, they would get converted to lines.
+ // Use PolyPolygon::Clip().
+ assert( !HasFlags());
+
+ // #105251# Normalize rect before edge filtering
+ tools::Rectangle aJustifiedRect( rRect );
+ aJustifiedRect.Normalize();
+
+ sal_uInt16 nSourceSize = mpImplPolygon->mnPoints;
+ ImplPolygonPointFilter aPolygon( nSourceSize );
+ ImplEdgePointFilter aHorzFilter( EDGE_HORZ, aJustifiedRect.Left(), aJustifiedRect.Right(),
+ aPolygon );
+ ImplEdgePointFilter aVertFilter( EDGE_VERT, aJustifiedRect.Top(), aJustifiedRect.Bottom(),
+ aHorzFilter );
+
+ for ( sal_uInt16 i = 0; i < nSourceSize; i++ )
+ aVertFilter.Input( mpImplPolygon->mxPointAry[i] );
+ if ( aVertFilter.IsPolygon() )
+ aVertFilter.LastPoint();
+ else
+ aPolygon.LastPoint();
+
+ mpImplPolygon = ImplType(aPolygon.get());
+}
+
+tools::Rectangle Polygon::GetBoundRect() const
+{
+ // Removing the assert. Bezier curves have the attribute that each single
+ // curve segment defined by four points can not exit the four-point polygon
+ // defined by that points. This allows to say that the curve segment can also
+ // never leave the Range of its defining points.
+ // The result is that Polygon::GetBoundRect() may not create the minimal
+ // BoundRect of the Polygon (to get that, use basegfx::B2DPolygon classes),
+ // but will always create a valid BoundRect, at least as long as this method
+ // 'blindly' travels over all points, including control points.
+
+ // DBG_ASSERT( !mpImplPolygon->mxFlagAry.get(), "GetBoundRect could fail with beziers!" );
+
+ sal_uInt16 nCount = mpImplPolygon->mnPoints;
+ if( ! nCount )
+ return tools::Rectangle();
+
+ tools::Long nXMin, nXMax, nYMin, nYMax;
+
+ const Point& pFirstPt = mpImplPolygon->mxPointAry[0];
+ nXMin = nXMax = pFirstPt.X();
+ nYMin = nYMax = pFirstPt.Y();
+
+ for ( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ const Point& rPt = mpImplPolygon->mxPointAry[i];
+
+ if (rPt.X() < nXMin)
+ nXMin = rPt.X();
+ if (rPt.X() > nXMax)
+ nXMax = rPt.X();
+ if (rPt.Y() < nYMin)
+ nYMin = rPt.Y();
+ if (rPt.Y() > nYMax)
+ nYMax = rPt.Y();
+ }
+
+ return tools::Rectangle( nXMin, nYMin, nXMax, nYMax );
+}
+
+bool Polygon::Contains( const Point& rPoint ) const
+{
+ DBG_ASSERT( !mpImplPolygon->mxFlagAry, "IsInside could fail with beziers!" );
+
+ const tools::Rectangle aBound( GetBoundRect() );
+ const Line aLine( rPoint, Point( aBound.Right() + 100, rPoint.Y() ) );
+ sal_uInt16 nCount = mpImplPolygon->mnPoints;
+ sal_uInt16 nPCounter = 0;
+
+ if ( ( nCount > 2 ) && aBound.Contains( rPoint ) )
+ {
+ Point aPt1( mpImplPolygon->mxPointAry[ 0 ] );
+ Point aIntersection;
+ Point aLastIntersection;
+
+ while ( ( aPt1 == mpImplPolygon->mxPointAry[ nCount - 1 ] ) && ( nCount > 3 ) )
+ nCount--;
+
+ for ( sal_uInt16 i = 1; i <= nCount; i++ )
+ {
+ const Point& rPt2 = mpImplPolygon->mxPointAry[ ( i < nCount ) ? i : 0 ];
+
+ if ( aLine.Intersection( Line( aPt1, rPt2 ), aIntersection ) )
+ {
+ // This avoids insertion of double intersections
+ if ( nPCounter )
+ {
+ if ( aIntersection != aLastIntersection )
+ {
+ aLastIntersection = aIntersection;
+ nPCounter++;
+ }
+ }
+ else
+ {
+ aLastIntersection = aIntersection;
+ nPCounter++;
+ }
+ }
+
+ aPt1 = rPt2;
+ }
+ }
+
+ // is inside, if number of intersection points is odd
+ return ( ( nPCounter & 1 ) == 1 );
+}
+
+void Polygon::Insert( sal_uInt16 nPos, const Point& rPt )
+{
+ if( nPos >= mpImplPolygon->mnPoints )
+ nPos = mpImplPolygon->mnPoints;
+
+ if (mpImplPolygon->ImplSplit(nPos, 1))
+ mpImplPolygon->mxPointAry[ nPos ] = rPt;
+}
+
+void Polygon::Insert( sal_uInt16 nPos, const tools::Polygon& rPoly )
+{
+ const sal_uInt16 nInsertCount = rPoly.mpImplPolygon->mnPoints;
+
+ if( nInsertCount )
+ {
+ if( nPos >= mpImplPolygon->mnPoints )
+ nPos = mpImplPolygon->mnPoints;
+
+ if (rPoly.mpImplPolygon->mxFlagAry)
+ mpImplPolygon->ImplCreateFlagArray();
+
+ mpImplPolygon->ImplSplit( nPos, nInsertCount, rPoly.mpImplPolygon.get() );
+ }
+}
+
+Point& Polygon::operator[]( sal_uInt16 nPos )
+{
+ assert( nPos < mpImplPolygon->mnPoints );
+
+ return mpImplPolygon->mxPointAry[nPos];
+}
+
+tools::Polygon& Polygon::operator=( const tools::Polygon& rPoly )
+{
+ mpImplPolygon = rPoly.mpImplPolygon;
+ return *this;
+}
+
+tools::Polygon& Polygon::operator=( tools::Polygon&& rPoly ) noexcept
+{
+ mpImplPolygon = std::move(rPoly.mpImplPolygon);
+ return *this;
+}
+
+bool Polygon::operator==( const tools::Polygon& rPoly ) const
+{
+ return (mpImplPolygon == rPoly.mpImplPolygon);
+}
+
+bool Polygon::IsEqual( const tools::Polygon& rPoly ) const
+{
+ bool bIsEqual = true;
+ sal_uInt16 i;
+ if ( GetSize() != rPoly.GetSize() )
+ bIsEqual = false;
+ else
+ {
+ for ( i = 0; i < GetSize(); i++ )
+ {
+ if ( ( GetPoint( i ) != rPoly.GetPoint( i ) ) ||
+ ( GetFlags( i ) != rPoly.GetFlags( i ) ) )
+ {
+ bIsEqual = false;
+ break;
+ }
+ }
+ }
+ return bIsEqual;
+}
+
+SvStream& ReadPolygon( SvStream& rIStream, tools::Polygon& rPoly )
+{
+ sal_uInt16 nPoints(0);
+
+ // read all points and create array
+ rIStream.ReadUInt16( nPoints );
+
+ const size_t nMaxRecordsPossible = rIStream.remainingSize() / (2 * sizeof(sal_Int32));
+ if (nPoints > nMaxRecordsPossible)
+ {
+ SAL_WARN("tools", "Polygon claims " << nPoints << " records, but only " << nMaxRecordsPossible << " possible");
+ nPoints = nMaxRecordsPossible;
+ }
+
+ rPoly.mpImplPolygon->ImplSetSize( nPoints, false );
+
+ for (sal_uInt16 i = 0; i < nPoints; i++)
+ {
+ sal_Int32 nTmpX(0), nTmpY(0);
+ rIStream.ReadInt32(nTmpX).ReadInt32(nTmpY);
+ rPoly.mpImplPolygon->mxPointAry[i].setX(nTmpX);
+ rPoly.mpImplPolygon->mxPointAry[i].setY(nTmpY);
+ }
+
+ return rIStream;
+}
+
+SvStream& WritePolygon( SvStream& rOStream, const tools::Polygon& rPoly )
+{
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ // Write number of points
+ rOStream.WriteUInt16( nPoints );
+
+ for (sal_uInt16 i = 0; i < nPoints; i++)
+ {
+ rOStream.WriteInt32(rPoly.mpImplPolygon->mxPointAry[i].X())
+ .WriteInt32(rPoly.mpImplPolygon->mxPointAry[i].Y());
+ }
+
+ return rOStream;
+}
+
+void Polygon::ImplRead( SvStream& rIStream )
+{
+ sal_uInt8 bHasPolyFlags(0);
+
+ ReadPolygon( rIStream, *this );
+ rIStream.ReadUChar( bHasPolyFlags );
+
+ if ( bHasPolyFlags )
+ {
+ mpImplPolygon->mxFlagAry.reset(new PolyFlags[mpImplPolygon->mnPoints]);
+ auto nRead = rIStream.ReadBytes(mpImplPolygon->mxFlagAry.get(), mpImplPolygon->mnPoints);
+ if (nRead != mpImplPolygon->mnPoints)
+ {
+ SAL_WARN("tools", "Short read");
+ memset(mpImplPolygon->mxFlagAry.get() + nRead, 0, mpImplPolygon->mnPoints - nRead);
+ }
+ }
+}
+
+void Polygon::Read( SvStream& rIStream )
+{
+ VersionCompatRead aCompat(rIStream);
+
+ ImplRead( rIStream );
+}
+
+void Polygon::ImplWrite( SvStream& rOStream ) const
+{
+ bool bHasPolyFlags(mpImplPolygon->mxFlagAry);
+ WritePolygon( rOStream, *this );
+ rOStream.WriteBool(bHasPolyFlags);
+
+ if ( bHasPolyFlags )
+ rOStream.WriteBytes(mpImplPolygon->mxFlagAry.get(), mpImplPolygon->mnPoints);
+}
+
+void Polygon::Write( SvStream& rOStream ) const
+{
+ VersionCompatWrite aCompat(rOStream, 1);
+
+ ImplWrite( rOStream );
+}
+
+// #i74631#/#i115917# numerical correction method for B2DPolygon
+static void impCorrectContinuity(basegfx::B2DPolygon& roPolygon, sal_uInt32 nIndex, PolyFlags nCFlag)
+{
+ const sal_uInt32 nPointCount(roPolygon.count());
+ OSL_ENSURE(nIndex < nPointCount, "impCorrectContinuity: index access out of range (!)");
+
+ if(nIndex >= nPointCount || (PolyFlags::Smooth != nCFlag && PolyFlags::Symmetric != nCFlag))
+ return;
+
+ if(!roPolygon.isPrevControlPointUsed(nIndex) || !roPolygon.isNextControlPointUsed(nIndex))
+ return;
+
+ // #i115917# Patch from osnola (modified, thanks for showing the problem)
+
+ // The correction is needed because an integer polygon with control points
+ // is converted to double precision. When C1 or C2 is used the involved vectors
+ // may not have the same directions/lengths since these come from integer coordinates
+ // and may have been snapped to different nearest integer coordinates. The snap error
+ // is in the range of +-1 in y and y, thus 0.0 <= error <= sqrt(2.0). Nonetheless,
+ // it needs to be corrected to be able to detect the continuity in this points
+ // correctly.
+
+ // We only have the integer data here (already in double precision form, but no mantissa
+ // used), so the best correction is to use:
+
+ // for C1: The longest vector since it potentially has best preserved the original vector.
+ // Even better the sum of the vectors, weighted by their length. This gives the
+ // normal vector addition to get the vector itself, lengths need to be preserved.
+ // for C2: The mediated vector(s) since both should be the same, but mirrored
+
+ // extract the point and vectors
+ const basegfx::B2DPoint aPoint(roPolygon.getB2DPoint(nIndex));
+ const basegfx::B2DVector aNext(roPolygon.getNextControlPoint(nIndex) - aPoint);
+ const basegfx::B2DVector aPrev(aPoint - roPolygon.getPrevControlPoint(nIndex));
+
+ // calculate common direction vector, normalize
+ const basegfx::B2DVector aDirection(aNext + aPrev);
+ const double fDirectionLen = aDirection.getLength();
+ if (fDirectionLen == 0.0)
+ return;
+
+ if (PolyFlags::Smooth == nCFlag)
+ {
+ // C1: apply common direction vector, preserve individual lengths
+ const double fInvDirectionLen(1.0 / fDirectionLen);
+ roPolygon.setNextControlPoint(nIndex, basegfx::B2DPoint(aPoint + (aDirection * (aNext.getLength() * fInvDirectionLen))));
+ roPolygon.setPrevControlPoint(nIndex, basegfx::B2DPoint(aPoint - (aDirection * (aPrev.getLength() * fInvDirectionLen))));
+ }
+ else // PolyFlags::Symmetric
+ {
+ // C2: get mediated length. Taking half of the unnormalized direction would be
+ // an approximation, but not correct.
+ const double fMedLength((aNext.getLength() + aPrev.getLength()) * (0.5 / fDirectionLen));
+ const basegfx::B2DVector aScaledDirection(aDirection * fMedLength);
+
+ // Bring Direction to correct length and apply
+ roPolygon.setNextControlPoint(nIndex, basegfx::B2DPoint(aPoint + aScaledDirection));
+ roPolygon.setPrevControlPoint(nIndex, basegfx::B2DPoint(aPoint - aScaledDirection));
+ }
+}
+
+// convert to basegfx::B2DPolygon and return
+basegfx::B2DPolygon Polygon::getB2DPolygon() const
+{
+ basegfx::B2DPolygon aRetval;
+ const sal_uInt16 nCount(mpImplPolygon->mnPoints);
+
+ if (nCount)
+ {
+ if (mpImplPolygon->mxFlagAry)
+ {
+ // handling for curves. Add start point
+ const Point aStartPoint(mpImplPolygon->mxPointAry[0]);
+ PolyFlags nPointFlag(mpImplPolygon->mxFlagAry[0]);
+ aRetval.append(basegfx::B2DPoint(aStartPoint.X(), aStartPoint.Y()));
+ Point aControlA, aControlB;
+
+ for(sal_uInt16 a(1); a < nCount;)
+ {
+ bool bControlA(false);
+ bool bControlB(false);
+
+ if(PolyFlags::Control == mpImplPolygon->mxFlagAry[a])
+ {
+ aControlA = mpImplPolygon->mxPointAry[a++];
+ bControlA = true;
+ }
+
+ if(a < nCount && PolyFlags::Control == mpImplPolygon->mxFlagAry[a])
+ {
+ aControlB = mpImplPolygon->mxPointAry[a++];
+ bControlB = true;
+ }
+
+ // assert invalid polygons
+ OSL_ENSURE(bControlA == bControlB, "Polygon::getB2DPolygon: Invalid source polygon (!)");
+
+ if(a < nCount)
+ {
+ const Point aEndPoint(mpImplPolygon->mxPointAry[a]);
+
+ if(bControlA)
+ {
+ // bezier edge, add
+ aRetval.appendBezierSegment(
+ basegfx::B2DPoint(aControlA.X(), aControlA.Y()),
+ basegfx::B2DPoint(aControlB.X(), aControlB.Y()),
+ basegfx::B2DPoint(aEndPoint.X(), aEndPoint.Y()));
+
+ impCorrectContinuity(aRetval, aRetval.count() - 2, nPointFlag);
+ }
+ else
+ {
+ // no bezier edge, add end point
+ aRetval.append(basegfx::B2DPoint(aEndPoint.X(), aEndPoint.Y()));
+ }
+
+ nPointFlag = mpImplPolygon->mxFlagAry[a++];
+ }
+ }
+
+ // if exist, remove double first/last points, set closed and correct control points
+ basegfx::utils::checkClosed(aRetval);
+
+ if(aRetval.isClosed())
+ {
+ // closeWithGeometryChange did really close, so last point(s) were removed.
+ // Correct the continuity in the changed point
+ impCorrectContinuity(aRetval, 0, mpImplPolygon->mxFlagAry[0]);
+ }
+ }
+ else
+ {
+ // extra handling for non-curves (most-used case) for speedup
+ for(sal_uInt16 a(0); a < nCount; a++)
+ {
+ // get point and add
+ const Point aPoint(mpImplPolygon->mxPointAry[a]);
+ aRetval.append(basegfx::B2DPoint(aPoint.X(), aPoint.Y()));
+ }
+
+ // set closed flag
+ basegfx::utils::checkClosed(aRetval);
+ }
+ }
+
+ return aRetval;
+}
+
+Polygon::Polygon(const basegfx::B2DPolygon& rPolygon) : mpImplPolygon(ImplPolygon(rPolygon))
+{
+}
+
+} // namespace tools
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/poly2.cxx b/tools/source/generic/poly2.cxx
new file mode 100644
index 0000000000..a00f64f41b
--- /dev/null
+++ b/tools/source/generic/poly2.cxx
@@ -0,0 +1,520 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <poly.h>
+#include <tools/poly.hxx>
+#include <tools/debug.hxx>
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+#include <tools/gen.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+
+namespace tools {
+
+PolyPolygon::PolyPolygon( sal_uInt16 nInitSize )
+ : mpImplPolyPolygon( ImplPolyPolygon( nInitSize ) )
+{
+}
+
+PolyPolygon::PolyPolygon( const tools::Polygon& rPoly )
+ : mpImplPolyPolygon( rPoly )
+{
+}
+PolyPolygon::PolyPolygon( const tools::Rectangle& rRect )
+ : mpImplPolyPolygon( tools::Polygon(rRect) )
+{
+}
+
+PolyPolygon::PolyPolygon( const tools::PolyPolygon& rPolyPoly )
+ : mpImplPolyPolygon( rPolyPoly.mpImplPolyPolygon )
+{
+}
+
+PolyPolygon::PolyPolygon( tools::PolyPolygon&& rPolyPoly ) noexcept
+ : mpImplPolyPolygon( std::move(rPolyPoly.mpImplPolyPolygon) )
+{
+}
+
+PolyPolygon::~PolyPolygon()
+{
+}
+
+void PolyPolygon::Insert( const tools::Polygon& rPoly, sal_uInt16 nPos )
+{
+ assert ( mpImplPolyPolygon->mvPolyAry.size() < MAX_POLYGONS );
+
+ if ( nPos > mpImplPolyPolygon->mvPolyAry.size() )
+ nPos = mpImplPolyPolygon->mvPolyAry.size();
+
+ mpImplPolyPolygon->mvPolyAry.insert(mpImplPolyPolygon->mvPolyAry.begin() + nPos, rPoly);
+}
+
+void PolyPolygon::Remove( sal_uInt16 nPos )
+{
+ assert(nPos < Count() && "PolyPolygon::Remove(): nPos >= nSize");
+
+ mpImplPolyPolygon->mvPolyAry.erase(mpImplPolyPolygon->mvPolyAry.begin() + nPos);
+}
+
+void PolyPolygon::Replace( const tools::Polygon& rPoly, sal_uInt16 nPos )
+{
+ assert(nPos < Count() && "PolyPolygon::Replace(): nPos >= nSize");
+
+ mpImplPolyPolygon->mvPolyAry[nPos] = rPoly;
+}
+
+const tools::Polygon& PolyPolygon::GetObject( sal_uInt16 nPos ) const
+{
+ assert(nPos < Count() && "PolyPolygon::GetObject(): nPos >= nSize");
+
+ return mpImplPolyPolygon->mvPolyAry[nPos];
+}
+
+bool PolyPolygon::IsRect() const
+{
+ bool bIsRect = false;
+ if ( Count() == 1 )
+ bIsRect = mpImplPolyPolygon->mvPolyAry[ 0 ].IsRect();
+ return bIsRect;
+}
+
+void PolyPolygon::Clear()
+{
+ mpImplPolyPolygon->mvPolyAry.clear();
+}
+
+void PolyPolygon::Optimize( PolyOptimizeFlags nOptimizeFlags )
+{
+ if(!(bool(nOptimizeFlags) && Count()))
+ return;
+
+ // #115630# ImplDrawHatch does not work with beziers included in the polypolygon, take care of that
+ bool bIsCurve(false);
+
+ for(sal_uInt16 a(0); !bIsCurve && a < Count(); a++)
+ {
+ if((*this)[a].HasFlags())
+ {
+ bIsCurve = true;
+ }
+ }
+
+ if(bIsCurve)
+ {
+ OSL_ENSURE(false, "Optimize does *not* support curves, falling back to AdaptiveSubdivide()...");
+ tools::PolyPolygon aPolyPoly;
+
+ AdaptiveSubdivide(aPolyPoly);
+ aPolyPoly.Optimize(nOptimizeFlags);
+ *this = aPolyPoly;
+ }
+ else
+ {
+ double fArea;
+ const bool bEdges = ( nOptimizeFlags & PolyOptimizeFlags::EDGES ) == PolyOptimizeFlags::EDGES;
+ sal_uInt16 nPercent = 0;
+
+ if( bEdges )
+ {
+ const tools::Rectangle aBound( GetBoundRect() );
+
+ fArea = ( aBound.GetWidth() + aBound.GetHeight() ) * 0.5;
+ nPercent = 50;
+ nOptimizeFlags &= ~PolyOptimizeFlags::EDGES;
+ }
+
+ // Optimize polygons
+ for( sal_uInt16 i = 0, nPolyCount = mpImplPolyPolygon->mvPolyAry.size(); i < nPolyCount; i++ )
+ {
+ if( bEdges )
+ {
+ mpImplPolyPolygon->mvPolyAry[ i ].Optimize( PolyOptimizeFlags::NO_SAME );
+ tools::Polygon::ImplReduceEdges( mpImplPolyPolygon->mvPolyAry[ i ], fArea, nPercent );
+ }
+
+ if( bool(nOptimizeFlags) )
+ mpImplPolyPolygon->mvPolyAry[ i ].Optimize( nOptimizeFlags );
+ }
+ }
+}
+
+void PolyPolygon::AdaptiveSubdivide( tools::PolyPolygon& rResult ) const
+{
+ rResult.Clear();
+
+ tools::Polygon aPolygon;
+
+ for( size_t i = 0; i < mpImplPolyPolygon->mvPolyAry.size(); i++ )
+ {
+ mpImplPolyPolygon->mvPolyAry[ i ].AdaptiveSubdivide( aPolygon, 1.0 );
+ rResult.Insert( aPolygon );
+ }
+}
+
+tools::PolyPolygon PolyPolygon::SubdivideBezier( const tools::PolyPolygon& rPolyPoly )
+{
+ sal_uInt16 i, nPolys = rPolyPoly.Count();
+ tools::PolyPolygon aPolyPoly( nPolys );
+ for( i=0; i<nPolys; ++i )
+ aPolyPoly.Insert( tools::Polygon::SubdivideBezier( rPolyPoly.GetObject(i) ) );
+
+ return aPolyPoly;
+}
+
+
+void PolyPolygon::GetIntersection( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult ) const
+{
+ ImplDoOperation( rPolyPoly, rResult, PolyClipOp::INTERSECT );
+}
+
+void PolyPolygon::GetUnion( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult ) const
+{
+ ImplDoOperation( rPolyPoly, rResult, PolyClipOp::UNION );
+}
+
+void PolyPolygon::ImplDoOperation( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rResult, PolyClipOp nOperation ) const
+{
+ // Convert to B2DPolyPolygon, temporarily. It might be
+ // advantageous in the future, to have a tools::PolyPolygon adaptor that
+ // just simulates a B2DPolyPolygon here...
+ basegfx::B2DPolyPolygon aMergePolyPolygonA( getB2DPolyPolygon() );
+ basegfx::B2DPolyPolygon aMergePolyPolygonB( rPolyPoly.getB2DPolyPolygon() );
+
+ // normalize the two polypolygons before. Force properly oriented
+ // polygons.
+ aMergePolyPolygonA = basegfx::utils::prepareForPolygonOperation( aMergePolyPolygonA );
+ aMergePolyPolygonB = basegfx::utils::prepareForPolygonOperation( aMergePolyPolygonB );
+
+ switch( nOperation )
+ {
+ // All code extracted from svx/source/svdraw/svedtv2.cxx
+
+ case PolyClipOp::UNION:
+ {
+ // merge A and B (OR)
+ aMergePolyPolygonA = basegfx::utils::solvePolygonOperationOr(aMergePolyPolygonA, aMergePolyPolygonB);
+ break;
+ }
+
+ default:
+ case PolyClipOp::INTERSECT:
+ {
+ // cut poly 1 against polys 2..n (AND)
+ aMergePolyPolygonA = basegfx::utils::solvePolygonOperationAnd(aMergePolyPolygonA, aMergePolyPolygonB);
+ break;
+ }
+ }
+
+ rResult = tools::PolyPolygon( aMergePolyPolygonA );
+}
+
+sal_uInt16 PolyPolygon::Count() const
+{
+ return mpImplPolyPolygon->mvPolyAry.size();
+}
+
+void PolyPolygon::Move( tools::Long nHorzMove, tools::Long nVertMove )
+{
+ // Required for DrawEngine
+ if( nHorzMove || nVertMove )
+ {
+ // move points
+ sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size();
+ for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[i].Move( nHorzMove, nVertMove );
+ }
+}
+
+void PolyPolygon::Translate( const Point& rTrans )
+{
+ // move points
+ for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[ i ].Translate( rTrans );
+}
+
+void PolyPolygon::Scale( double fScaleX, double fScaleY )
+{
+ // Move points
+ for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[ i ].Scale( fScaleX, fScaleY );
+}
+
+void PolyPolygon::Rotate( const Point& rCenter, Degree10 nAngle10 )
+{
+ nAngle10 %= 3600_deg10;
+
+ if( nAngle10 )
+ {
+ const double fAngle = toRadians(nAngle10);
+ Rotate( rCenter, sin( fAngle ), cos( fAngle ) );
+ }
+}
+
+void PolyPolygon::Rotate( const Point& rCenter, double fSin, double fCos )
+{
+ // move points
+ for ( sal_uInt16 i = 0, nCount = mpImplPolyPolygon->mvPolyAry.size(); i < nCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[ i ].Rotate( rCenter, fSin, fCos );
+}
+
+void PolyPolygon::Clip( const tools::Rectangle& rRect )
+{
+ sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size();
+ sal_uInt16 i;
+
+ if ( !nPolyCount )
+ return;
+
+ // If there are bezier curves involved, Polygon::Clip() is broken.
+ // Use a temporary B2DPolyPolygon for the clipping.
+ for ( i = 0; i < nPolyCount; i++ )
+ {
+ if(mpImplPolyPolygon->mvPolyAry[i].HasFlags())
+ {
+ const basegfx::B2DPolyPolygon aPoly(
+ basegfx::utils::clipPolyPolygonOnRange(
+ getB2DPolyPolygon(),
+ basegfx::B2DRange(
+ rRect.Left(),
+ rRect.Top(),
+ rRect.Right() + 1,
+ rRect.Bottom() + 1),
+ true,
+ false));
+ *this = PolyPolygon( aPoly );
+ return;
+ }
+ }
+
+ // Clip every polygon, deleting the empty ones
+ for ( i = 0; i < nPolyCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[i].Clip( rRect );
+ while ( nPolyCount )
+ {
+ if ( GetObject( nPolyCount-1 ).GetSize() <= 2 )
+ Remove( nPolyCount-1 );
+ nPolyCount--;
+ }
+}
+
+tools::Rectangle PolyPolygon::GetBoundRect() const
+{
+ tools::Long nXMin=0, nXMax=0, nYMin=0, nYMax=0;
+ bool bFirst = true;
+ sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size();
+
+ for ( sal_uInt16 n = 0; n < nPolyCount; n++ )
+ {
+ const tools::Polygon* pPoly = &mpImplPolyPolygon->mvPolyAry[n];
+ const Point* pAry = pPoly->GetConstPointAry();
+ sal_uInt16 nPointCount = pPoly->GetSize();
+
+ for ( sal_uInt16 i = 0; i < nPointCount; i++ )
+ {
+ const Point* pPt = &pAry[ i ];
+
+ if ( bFirst )
+ {
+ nXMin = nXMax = pPt->X();
+ nYMin = nYMax = pPt->Y();
+ bFirst = false;
+ }
+ else
+ {
+ if ( pPt->X() < nXMin )
+ nXMin = pPt->X();
+ if ( pPt->X() > nXMax )
+ nXMax = pPt->X();
+ if ( pPt->Y() < nYMin )
+ nYMin = pPt->Y();
+ if ( pPt->Y() > nYMax )
+ nYMax = pPt->Y();
+ }
+ }
+ }
+
+ if ( !bFirst )
+ return tools::Rectangle( nXMin, nYMin, nXMax, nYMax );
+ else
+ return tools::Rectangle();
+}
+
+Polygon& PolyPolygon::operator[]( sal_uInt16 nPos )
+{
+ assert(nPos < Count() && "PolyPolygon::[](): nPos >= nSize");
+
+ return mpImplPolyPolygon->mvPolyAry[nPos];
+}
+
+PolyPolygon& PolyPolygon::operator=( const tools::PolyPolygon& rPolyPoly )
+{
+ mpImplPolyPolygon = rPolyPoly.mpImplPolyPolygon;
+ return *this;
+}
+
+PolyPolygon& PolyPolygon::operator=( tools::PolyPolygon&& rPolyPoly ) noexcept
+{
+ mpImplPolyPolygon = std::move(rPolyPoly.mpImplPolyPolygon);
+ return *this;
+}
+
+bool PolyPolygon::operator==( const tools::PolyPolygon& rPolyPoly ) const
+{
+ return rPolyPoly.mpImplPolyPolygon == mpImplPolyPolygon;
+}
+
+SvStream& ReadPolyPolygon( SvStream& rIStream, tools::PolyPolygon& rPolyPoly )
+{
+ sal_uInt16 nPolyCount(0);
+
+ // Read number of polygons
+ rIStream.ReadUInt16( nPolyCount );
+
+ const size_t nMinRecordSize = sizeof(sal_uInt16);
+ const size_t nMaxRecords = rIStream.remainingSize() / nMinRecordSize;
+ if (nPolyCount > nMaxRecords)
+ {
+ SAL_WARN("tools", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nPolyCount << " claimed, truncating");
+ nPolyCount = nMaxRecords;
+ }
+
+ if( nPolyCount )
+ {
+ rPolyPoly.mpImplPolyPolygon->mvPolyAry.resize(nPolyCount);
+
+ tools::Polygon aTempPoly;
+ for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
+ {
+ ReadPolygon( rIStream, aTempPoly );
+ rPolyPoly.mpImplPolyPolygon->mvPolyAry[i] = aTempPoly;
+ }
+ }
+ else
+ rPolyPoly = tools::PolyPolygon();
+
+ return rIStream;
+}
+
+SvStream& WritePolyPolygon( SvStream& rOStream, const tools::PolyPolygon& rPolyPoly )
+{
+ // Write number of polygons
+ sal_uInt16 nPolyCount = rPolyPoly.mpImplPolyPolygon->mvPolyAry.size();
+ rOStream.WriteUInt16( nPolyCount );
+
+ // output polygons
+ for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
+ WritePolygon( rOStream, rPolyPoly.mpImplPolyPolygon->mvPolyAry[i] );
+
+ return rOStream;
+}
+
+void PolyPolygon::Read( SvStream& rIStream )
+{
+ VersionCompatRead aCompat(rIStream);
+
+ sal_uInt16 nPolyCount(0);
+
+ // Read number of polygons
+ rIStream.ReadUInt16( nPolyCount );
+
+ const size_t nMinRecordSize = sizeof(sal_uInt16);
+ const size_t nMaxRecords = rIStream.remainingSize() / nMinRecordSize;
+ if (nPolyCount > nMaxRecords)
+ {
+ SAL_WARN("tools", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nPolyCount << " claimed, truncating");
+ nPolyCount = nMaxRecords;
+ }
+
+ if( nPolyCount )
+ {
+ mpImplPolyPolygon->mvPolyAry.clear();
+
+ for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
+ {
+ tools::Polygon aTempPoly;
+ aTempPoly.ImplRead( rIStream );
+ mpImplPolyPolygon->mvPolyAry.emplace_back( aTempPoly );
+ }
+ }
+ else
+ *this = tools::PolyPolygon();
+}
+
+void PolyPolygon::Write( SvStream& rOStream ) const
+{
+ VersionCompatWrite aCompat(rOStream, 1);
+
+ // Write number of polygons
+ sal_uInt16 nPolyCount = mpImplPolyPolygon->mvPolyAry.size();
+ rOStream.WriteUInt16( nPolyCount );
+
+ // Output polygons
+ for ( sal_uInt16 i = 0; i < nPolyCount; i++ )
+ mpImplPolyPolygon->mvPolyAry[i].ImplWrite( rOStream );
+}
+
+// convert to basegfx::B2DPolyPolygon and return
+basegfx::B2DPolyPolygon PolyPolygon::getB2DPolyPolygon() const
+{
+ basegfx::B2DPolyPolygon aRetval;
+
+ for(size_t a(0); a < mpImplPolyPolygon->mvPolyAry.size(); a++)
+ {
+ tools::Polygon const & rCandidate = mpImplPolyPolygon->mvPolyAry[a];
+ aRetval.append(rCandidate.getB2DPolygon());
+ }
+
+ return aRetval;
+}
+
+// constructor to convert from basegfx::B2DPolyPolygon
+PolyPolygon::PolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon)
+ : mpImplPolyPolygon(rPolyPolygon)
+{
+}
+
+} /* namespace tools */
+
+ImplPolyPolygon::ImplPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon)
+{
+ const sal_uInt16 nCount(sal_uInt16(rPolyPolygon.count()));
+ DBG_ASSERT(sal_uInt32(nCount) == rPolyPolygon.count(),
+ "PolyPolygon::PolyPolygon: Too many sub-polygons in given basegfx::B2DPolyPolygon (!)");
+
+ if ( nCount )
+ {
+ mvPolyAry.resize( nCount );
+
+ for(sal_uInt16 a(0); a < nCount; a++)
+ {
+ const basegfx::B2DPolygon& aCandidate(rPolyPolygon.getB2DPolygon(sal_uInt32(a)));
+ mvPolyAry[a] = tools::Polygon( aCandidate );
+ }
+ }
+ else
+ mvPolyAry.reserve(16);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/generic/svborder.cxx b/tools/source/generic/svborder.cxx
new file mode 100644
index 0000000000..a61c79b0e4
--- /dev/null
+++ b/tools/source/generic/svborder.cxx
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <tools/svborder.hxx>
+#include <tools/gen.hxx>
+
+tools::Rectangle& operator+=(tools::Rectangle& rRect, const SvBorder& rBorder)
+{
+ // call GetSize first due to Empty-Rect
+ Size aS(rRect.GetSize());
+ aS.AdjustWidth(rBorder.Left() + rBorder.Right());
+ aS.AdjustHeight(rBorder.Top() + rBorder.Bottom());
+
+ rRect.AdjustLeft(-(rBorder.Left()));
+ rRect.AdjustTop(-(rBorder.Top()));
+ rRect.SetSize(aS);
+ return rRect;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/inet/inetmime.cxx b/tools/source/inet/inetmime.cxx
new file mode 100644
index 0000000000..6694dc3986
--- /dev/null
+++ b/tools/source/inet/inetmime.cxx
@@ -0,0 +1,1387 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <algorithm>
+#include <limits>
+#include <forward_list>
+#include <memory>
+
+#include <sal/log.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/tencinfo.h>
+#include <tools/debug.hxx>
+#include <tools/inetmime.hxx>
+#include <rtl/character.hxx>
+
+namespace {
+
+rtl_TextEncoding getCharsetEncoding(const char * pBegin,
+ const char * pEnd);
+
+/** Check for US-ASCII white space character.
+
+ @param nChar Some UCS-4 character.
+
+ @return True if nChar is a US-ASCII white space character (US-ASCII
+ 0x09 or 0x20).
+ */
+bool isWhiteSpace(sal_uInt32 nChar)
+{
+ return nChar == '\t' || nChar == ' ';
+}
+
+/** Get the Base 64 digit weight of a US-ASCII character.
+
+ @param nChar Some UCS-4 character.
+
+ @return If nChar is a US-ASCII Base 64 digit character (US-ASCII
+ 'A'--'F', or 'a'--'f', '0'--'9', '+', or '/'), return the
+ corresponding weight (0--63); if nChar is the US-ASCII Base 64 padding
+ character (US-ASCII '='), return -1; otherwise, return -2.
+ */
+int getBase64Weight(sal_uInt32 nChar)
+{
+ return rtl::isAsciiUpperCase(nChar) ? int(nChar - 'A') :
+ rtl::isAsciiLowerCase(nChar) ? int(nChar - 'a' + 26) :
+ rtl::isAsciiDigit(nChar) ? int(nChar - '0' + 52) :
+ nChar == '+' ? 62 :
+ nChar == '/' ? 63 :
+ nChar == '=' ? -1 : -2;
+}
+
+bool startsWithLineFolding(const sal_Unicode * pBegin,
+ const sal_Unicode * pEnd)
+{
+ DBG_ASSERT(pBegin && pBegin <= pEnd,
+ "startsWithLineFolding(): Bad sequence");
+
+ return pEnd - pBegin >= 3 && pBegin[0] == 0x0D && pBegin[1] == 0x0A
+ && isWhiteSpace(pBegin[2]); // CR, LF
+}
+
+rtl_TextEncoding translateFromMIME(rtl_TextEncoding
+ eEncoding)
+{
+#if defined(_WIN32)
+ return eEncoding == RTL_TEXTENCODING_ISO_8859_1 ?
+ RTL_TEXTENCODING_MS_1252 : eEncoding;
+#else
+ return eEncoding;
+#endif
+}
+
+bool isMIMECharsetEncoding(rtl_TextEncoding eEncoding)
+{
+ return rtl_isOctetTextEncoding(eEncoding);
+}
+
+std::unique_ptr<sal_Unicode[]> convertToUnicode(const char * pBegin,
+ const char * pEnd,
+ rtl_TextEncoding eEncoding,
+ sal_Size & rSize)
+{
+ if (eEncoding == RTL_TEXTENCODING_DONTKNOW)
+ return nullptr;
+ rtl_TextToUnicodeConverter hConverter
+ = rtl_createTextToUnicodeConverter(eEncoding);
+ rtl_TextToUnicodeContext hContext
+ = rtl_createTextToUnicodeContext(hConverter);
+ std::unique_ptr<sal_Unicode[]> pBuffer;
+ sal_uInt32 nInfo;
+ for (sal_Size nBufferSize = pEnd - pBegin;;
+ nBufferSize += nBufferSize / 3 + 1)
+ {
+ pBuffer.reset(new sal_Unicode[nBufferSize]);
+ sal_Size nSrcCvtBytes;
+ rSize = rtl_convertTextToUnicode(
+ hConverter, hContext, pBegin, pEnd - pBegin, pBuffer.get(),
+ nBufferSize,
+ RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
+ | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
+ | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR,
+ &nInfo, &nSrcCvtBytes);
+ if (nInfo != RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL)
+ break;
+ pBuffer.reset();
+ rtl_resetTextToUnicodeContext(hConverter, hContext);
+ }
+ rtl_destroyTextToUnicodeContext(hConverter, hContext);
+ rtl_destroyTextToUnicodeConverter(hConverter);
+ if (nInfo != 0)
+ {
+ pBuffer.reset();
+ }
+ return pBuffer;
+}
+
+void writeUTF8(OStringBuffer & rSink, sal_uInt32 nChar)
+{
+ // See RFC 2279 for a discussion of UTF-8.
+ DBG_ASSERT(nChar < 0x80000000, "writeUTF8(): Bad char");
+
+ if (nChar < 0x80)
+ rSink.append(char(nChar));
+ else if (nChar < 0x800)
+ rSink.append(OStringChar(char(nChar >> 6 | 0xC0))
+ + OStringChar(char((nChar & 0x3F) | 0x80)));
+ else if (nChar < 0x10000)
+ rSink.append(
+ OStringChar(char(nChar >> 12 | 0xE0))
+ + OStringChar(char((nChar >> 6 & 0x3F) | 0x80))
+ + OStringChar(char((nChar & 0x3F) | 0x80)));
+ else if (nChar < 0x200000)
+ rSink.append(
+ OStringChar(char(nChar >> 18 | 0xF0))
+ + OStringChar(char((nChar >> 12 & 0x3F) | 0x80))
+ + OStringChar(char((nChar >> 6 & 0x3F) | 0x80))
+ + OStringChar(char((nChar & 0x3F) | 0x80)));
+ else if (nChar < 0x4000000)
+ rSink.append(
+ OStringChar(char(nChar >> 24 | 0xF8))
+ + OStringChar(char((nChar >> 18 & 0x3F) | 0x80))
+ + OStringChar(char((nChar >> 12 & 0x3F) | 0x80))
+ + OStringChar(char((nChar >> 6 & 0x3F) | 0x80))
+ + OStringChar(char((nChar & 0x3F) | 0x80)));
+ else
+ rSink.append(
+ OStringChar(char(nChar >> 30 | 0xFC))
+ + OStringChar(char((nChar >> 24 & 0x3F) | 0x80))
+ + OStringChar(char((nChar >> 18 & 0x3F) | 0x80))
+ + OStringChar(char((nChar >> 12 & 0x3F) | 0x80))
+ + OStringChar(char((nChar >> 6 & 0x3F) | 0x80))
+ + OStringChar(char((nChar & 0x3F) | 0x80)));
+}
+
+bool translateUTF8Char(const char *& rBegin,
+ const char * pEnd,
+ sal_uInt32 & rCharacter)
+{
+ if (rBegin == pEnd || static_cast< unsigned char >(*rBegin) < 0x80
+ || static_cast< unsigned char >(*rBegin) >= 0xFE)
+ return false;
+
+ int nCount;
+ sal_uInt32 nMin;
+ sal_uInt32 nUCS4;
+ const char * p = rBegin;
+ if (static_cast< unsigned char >(*p) < 0xE0)
+ {
+ nCount = 1;
+ nMin = 0x80;
+ nUCS4 = static_cast< unsigned char >(*p) & 0x1F;
+ }
+ else if (static_cast< unsigned char >(*p) < 0xF0)
+ {
+ nCount = 2;
+ nMin = 0x800;
+ nUCS4 = static_cast< unsigned char >(*p) & 0xF;
+ }
+ else if (static_cast< unsigned char >(*p) < 0xF8)
+ {
+ nCount = 3;
+ nMin = 0x10000;
+ nUCS4 = static_cast< unsigned char >(*p) & 7;
+ }
+ else if (static_cast< unsigned char >(*p) < 0xFC)
+ {
+ nCount = 4;
+ nMin = 0x200000;
+ nUCS4 = static_cast< unsigned char >(*p) & 3;
+ }
+ else
+ {
+ nCount = 5;
+ nMin = 0x4000000;
+ nUCS4 = static_cast< unsigned char >(*p) & 1;
+ }
+ ++p;
+
+ for (; nCount-- > 0; ++p)
+ if ((static_cast< unsigned char >(*p) & 0xC0) == 0x80)
+ nUCS4 = (nUCS4 << 6) | (static_cast< unsigned char >(*p) & 0x3F);
+ else
+ return false;
+
+ if (!rtl::isUnicodeCodePoint(nUCS4) || nUCS4 < nMin)
+ return false;
+
+ rCharacter = nUCS4;
+ rBegin = p;
+ return true;
+}
+
+void appendISO88591(OUStringBuffer & rText, char const * pBegin,
+ char const * pEnd);
+
+struct Parameter
+{
+ OString m_aAttribute;
+ OString m_aCharset;
+ OString m_aLanguage;
+ OString m_aValue;
+ sal_uInt32 m_nSection;
+ bool m_bExtended;
+
+ bool operator<(const Parameter& rhs) const // is used by std::list<Parameter>::sort
+ {
+ int nComp = m_aAttribute.compareTo(rhs.m_aAttribute);
+ return nComp < 0 ||
+ (nComp == 0 && m_nSection < rhs.m_nSection);
+ }
+ struct IsSameSection // is used to check container for duplicates with std::any_of
+ {
+ const OString& rAttribute;
+ const sal_uInt32 nSection;
+ bool operator()(const Parameter& r) const
+ { return r.m_aAttribute == rAttribute && r.m_nSection == nSection; }
+ };
+};
+
+typedef std::forward_list<Parameter> ParameterList;
+
+bool parseParameters(ParameterList const & rInput,
+ INetContentTypeParameterList * pOutput);
+
+// appendISO88591
+
+void appendISO88591(OUStringBuffer & rText, char const * pBegin,
+ char const * pEnd)
+{
+ sal_Int32 nLength = pEnd - pBegin;
+ std::unique_ptr<sal_Unicode[]> pBuffer(new sal_Unicode[nLength]);
+ for (sal_Unicode * p = pBuffer.get(); pBegin != pEnd;)
+ *p++ = static_cast<unsigned char>(*pBegin++);
+ rText.append(pBuffer.get(), nLength);
+}
+
+// parseParameters
+
+bool parseParameters(ParameterList const & rInput,
+ INetContentTypeParameterList * pOutput)
+{
+ if (pOutput)
+ pOutput->clear();
+
+ for (auto it = rInput.begin(), itPrev = rInput.end(); it != rInput.end() ; itPrev = it++)
+ {
+ if (it->m_nSection > 0
+ && (itPrev == rInput.end()
+ || itPrev->m_nSection != it->m_nSection - 1
+ || itPrev->m_aAttribute != it->m_aAttribute))
+ return false;
+ }
+
+ if (pOutput)
+ for (auto it = rInput.begin(), itNext = rInput.begin(); it != rInput.end(); it = itNext)
+ {
+ bool bCharset = !it->m_aCharset.isEmpty();
+ rtl_TextEncoding eEncoding = RTL_TEXTENCODING_DONTKNOW;
+ if (bCharset)
+ eEncoding
+ = getCharsetEncoding(it->m_aCharset.getStr(),
+ it->m_aCharset.getStr()
+ + it->m_aCharset.getLength());
+ OUStringBuffer aValue(64);
+ bool bBadEncoding = false;
+ itNext = it;
+ do
+ {
+ sal_Size nSize;
+ std::unique_ptr<sal_Unicode[]> pUnicode
+ = convertToUnicode(itNext->m_aValue.getStr(),
+ itNext->m_aValue.getStr()
+ + itNext->m_aValue.getLength(),
+ bCharset && it->m_bExtended ?
+ eEncoding :
+ RTL_TEXTENCODING_UTF8,
+ nSize);
+ if (!pUnicode && !(bCharset && it->m_bExtended))
+ pUnicode = convertToUnicode(
+ itNext->m_aValue.getStr(),
+ itNext->m_aValue.getStr()
+ + itNext->m_aValue.getLength(),
+ RTL_TEXTENCODING_ISO_8859_1, nSize);
+ if (!pUnicode)
+ {
+ bBadEncoding = true;
+ break;
+ }
+ aValue.append(pUnicode.get(), static_cast<sal_Int32>(nSize));
+ ++itNext;
+ }
+ while (itNext != rInput.end() && itNext->m_nSection != 0);
+
+ if (bBadEncoding)
+ {
+ aValue.setLength(0);
+ itNext = it;
+ do
+ {
+ if (itNext->m_bExtended)
+ {
+ for (sal_Int32 i = 0; i < itNext->m_aValue.getLength(); ++i)
+ aValue.append(
+ static_cast<sal_Unicode>(
+ static_cast<unsigned char>(itNext->m_aValue[i])
+ | 0xF800)); // map to unicode corporate use sub area
+ }
+ else
+ {
+ for (sal_Int32 i = 0; i < itNext->m_aValue.getLength(); ++i)
+ aValue.append( itNext->m_aValue[i] );
+ }
+ ++itNext;
+ }
+ while (itNext != rInput.end() && itNext->m_nSection != 0);
+ }
+ auto const ret = pOutput->insert(
+ {it->m_aAttribute,
+ {it->m_aCharset, it->m_aLanguage, aValue.makeStringAndClear(), !bBadEncoding}});
+ SAL_INFO_IF(!ret.second, "tools",
+ "INetMIME: dropping duplicate parameter: " << it->m_aAttribute);
+ }
+ return true;
+}
+
+/** Check whether some character is valid within an RFC 2045 <token>.
+
+ @param nChar Some UCS-4 character.
+
+ @return True if nChar is valid within an RFC 2047 <token> (US-ASCII
+ 'A'--'Z', 'a'--'z', '0'--'9', '!', '#', '$', '%', '&', ''', '*', '+',
+ '-', '.', '^', '_', '`', '{', '|', '}', or '~').
+ */
+bool isTokenChar(sal_uInt32 nChar)
+{
+ static const bool aMap[128]
+ = { false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, true, false, true, true, true, true, true, // !"#$%&'
+ false, false, true, true, false, true, true, false, //()*+,-./
+ true, true, true, true, true, true, true, true, //01234567
+ true, true, false, false, false, false, false, false, //89:;<=>?
+ false, true, true, true, true, true, true, true, //@ABCDEFG
+ true, true, true, true, true, true, true, true, //HIJKLMNO
+ true, true, true, true, true, true, true, true, //PQRSTUVW
+ true, true, true, false, false, false, true, true, //XYZ[\]^_
+ true, true, true, true, true, true, true, true, //`abcdefg
+ true, true, true, true, true, true, true, true, //hijklmno
+ true, true, true, true, true, true, true, true, //pqrstuvw
+ true, true, true, true, true, true, true, false //xyz{|}~
+ };
+ return rtl::isAscii(nChar) && aMap[nChar];
+}
+
+const sal_Unicode * skipComment(const sal_Unicode * pBegin,
+ const sal_Unicode * pEnd)
+{
+ DBG_ASSERT(pBegin && pBegin <= pEnd,
+ "skipComment(): Bad sequence");
+
+ if (pBegin != pEnd && *pBegin == '(')
+ {
+ sal_uInt32 nLevel = 0;
+ for (const sal_Unicode * p = pBegin; p != pEnd;)
+ switch (*p++)
+ {
+ case '(':
+ ++nLevel;
+ break;
+
+ case ')':
+ if (--nLevel == 0)
+ return p;
+ break;
+
+ case '\\':
+ if (p != pEnd)
+ ++p;
+ break;
+ }
+ }
+ return pBegin;
+}
+
+const sal_Unicode * skipLinearWhiteSpaceComment(const sal_Unicode *
+ pBegin,
+ const sal_Unicode *
+ pEnd)
+{
+ DBG_ASSERT(pBegin && pBegin <= pEnd,
+ "skipLinearWhiteSpaceComment(): Bad sequence");
+
+ while (pBegin != pEnd)
+ switch (*pBegin)
+ {
+ case '\t':
+ case ' ':
+ ++pBegin;
+ break;
+
+ case 0x0D: // CR
+ if (startsWithLineFolding(pBegin, pEnd))
+ pBegin += 3;
+ else
+ return pBegin;
+ break;
+
+ case '(':
+ {
+ const sal_Unicode * p = skipComment(pBegin, pEnd);
+ if (p == pBegin)
+ return pBegin;
+ pBegin = p;
+ break;
+ }
+
+ default:
+ return pBegin;
+ }
+ return pBegin;
+}
+
+const sal_Unicode * skipQuotedString(const sal_Unicode * pBegin,
+ const sal_Unicode * pEnd)
+{
+ DBG_ASSERT(pBegin && pBegin <= pEnd,
+ "skipQuotedString(): Bad sequence");
+
+ if (pBegin != pEnd && *pBegin == '"')
+ for (const sal_Unicode * p = pBegin + 1; p != pEnd;)
+ switch (*p++)
+ {
+ case 0x0D: // CR
+ if (pEnd - p < 2 || *p++ != 0x0A // LF
+ || !isWhiteSpace(*p++))
+ return pBegin;
+ break;
+
+ case '"':
+ return p;
+
+ case '\\':
+ if (p != pEnd)
+ ++p;
+ break;
+ }
+ return pBegin;
+}
+
+sal_Unicode const * scanParameters(sal_Unicode const * pBegin,
+ sal_Unicode const * pEnd,
+ INetContentTypeParameterList *
+ pParameters)
+{
+ ParameterList aList;
+ sal_Unicode const * pParameterBegin = pBegin;
+ for (sal_Unicode const * p = pParameterBegin;;)
+ {
+ pParameterBegin = skipLinearWhiteSpaceComment(p, pEnd);
+ if (pParameterBegin == pEnd || *pParameterBegin != ';')
+ break;
+ p = pParameterBegin + 1;
+
+ sal_Unicode const * pAttributeBegin
+ = skipLinearWhiteSpaceComment(p, pEnd);
+ p = pAttributeBegin;
+ bool bDowncaseAttribute = false;
+ while (p != pEnd && isTokenChar(*p) && *p != '*')
+ {
+ bDowncaseAttribute = bDowncaseAttribute || rtl::isAsciiUpperCase(*p);
+ ++p;
+ }
+ if (p == pAttributeBegin)
+ break;
+ OString aAttribute(pAttributeBegin, p - pAttributeBegin, RTL_TEXTENCODING_ASCII_US);
+ if (bDowncaseAttribute)
+ aAttribute = aAttribute.toAsciiLowerCase();
+
+ sal_uInt32 nSection = 0;
+ if (p != pEnd && *p == '*')
+ {
+ ++p;
+ if (p != pEnd && rtl::isAsciiDigit(*p)
+ && !INetMIME::scanUnsigned(p, pEnd, false, nSection))
+ break;
+ }
+
+ bool bPresent = std::any_of(aList.begin(), aList.end(),
+ Parameter::IsSameSection{aAttribute, nSection});
+ if (bPresent)
+ break;
+
+ bool bExtended = false;
+ if (p != pEnd && *p == '*')
+ {
+ ++p;
+ bExtended = true;
+ }
+
+ p = skipLinearWhiteSpaceComment(p, pEnd);
+
+ if (p == pEnd || *p != '=')
+ break;
+
+ p = skipLinearWhiteSpaceComment(p + 1, pEnd);
+
+ OString aCharset;
+ OString aLanguage;
+ OString aValue;
+ if (bExtended)
+ {
+ if (nSection == 0)
+ {
+ sal_Unicode const * pCharsetBegin = p;
+ bool bDowncaseCharset = false;
+ while (p != pEnd && isTokenChar(*p) && *p != '\'')
+ {
+ bDowncaseCharset = bDowncaseCharset || rtl::isAsciiUpperCase(*p);
+ ++p;
+ }
+ if (p == pCharsetBegin)
+ break;
+ if (pParameters)
+ {
+ aCharset = OString(
+ pCharsetBegin,
+ p - pCharsetBegin,
+ RTL_TEXTENCODING_ASCII_US);
+ if (bDowncaseCharset)
+ aCharset = aCharset.toAsciiLowerCase();
+ }
+
+ if (p == pEnd || *p != '\'')
+ break;
+ ++p;
+
+ sal_Unicode const * pLanguageBegin = p;
+ bool bDowncaseLanguage = false;
+ int nLetters = 0;
+ for (; p != pEnd; ++p)
+ if (rtl::isAsciiAlpha(*p))
+ {
+ if (++nLetters > 8)
+ break;
+ bDowncaseLanguage = bDowncaseLanguage
+ || rtl::isAsciiUpperCase(*p);
+ }
+ else if (*p == '-')
+ {
+ if (nLetters == 0)
+ break;
+ nLetters = 0;
+ }
+ else
+ break;
+ if (nLetters == 0 || nLetters > 8)
+ break;
+ if (pParameters)
+ {
+ aLanguage = OString(
+ pLanguageBegin,
+ p - pLanguageBegin,
+ RTL_TEXTENCODING_ASCII_US);
+ if (bDowncaseLanguage)
+ aLanguage = aLanguage.toAsciiLowerCase();
+ }
+
+ if (p == pEnd || *p != '\'')
+ break;
+ ++p;
+ }
+ if (pParameters)
+ {
+ OStringBuffer aSink;
+ while (p != pEnd)
+ {
+ auto q = p;
+ sal_uInt32 nChar = INetMIME::getUTF32Character(q, pEnd);
+ if (rtl::isAscii(nChar) && !isTokenChar(nChar))
+ break;
+ p = q;
+ if (nChar == '%' && p + 1 < pEnd)
+ {
+ int nWeight1 = INetMIME::getHexWeight(p[0]);
+ int nWeight2 = INetMIME::getHexWeight(p[1]);
+ if (nWeight1 >= 0 && nWeight2 >= 0)
+ {
+ aSink.append(char(nWeight1 << 4 | nWeight2));
+ p += 2;
+ continue;
+ }
+ }
+ writeUTF8(aSink, nChar);
+ }
+ aValue = aSink.makeStringAndClear();
+ }
+ else
+ while (p != pEnd && (isTokenChar(*p) || !rtl::isAscii(*p)))
+ ++p;
+ }
+ else if (p != pEnd && *p == '"')
+ if (pParameters)
+ {
+ OStringBuffer aSink(256);
+ bool bInvalid = false;
+ for (++p;;)
+ {
+ if (p == pEnd)
+ {
+ bInvalid = true;
+ break;
+ }
+ sal_uInt32 nChar = INetMIME::getUTF32Character(p, pEnd);
+ if (nChar == '"')
+ break;
+ else if (nChar == 0x0D) // CR
+ {
+ if (pEnd - p < 2 || *p++ != 0x0A // LF
+ || !isWhiteSpace(*p))
+ {
+ bInvalid = true;
+ break;
+ }
+ nChar = static_cast<unsigned char>(*p++);
+ }
+ else if (nChar == '\\')
+ {
+ if (p == pEnd)
+ {
+ bInvalid = true;
+ break;
+ }
+ nChar = INetMIME::getUTF32Character(p, pEnd);
+ }
+ writeUTF8(aSink, nChar);
+ }
+ if (bInvalid)
+ break;
+ aValue = aSink.makeStringAndClear();
+ }
+ else
+ {
+ sal_Unicode const * pStringEnd = skipQuotedString(p, pEnd);
+ if (p == pStringEnd)
+ break;
+ p = pStringEnd;
+ }
+ else
+ {
+ sal_Unicode const * pTokenBegin = p;
+ while (p != pEnd && (isTokenChar(*p) || !rtl::isAscii(*p)))
+ ++p;
+ if (p == pTokenBegin)
+ break;
+ if (pParameters)
+ aValue = OString(
+ pTokenBegin, p - pTokenBegin,
+ RTL_TEXTENCODING_UTF8);
+ }
+ aList.emplace_front(Parameter{aAttribute, aCharset, aLanguage, aValue, nSection, bExtended});
+ }
+ aList.sort();
+ return parseParameters(aList, pParameters) ? pParameterBegin : pBegin;
+}
+
+bool equalIgnoreCase(const char * pBegin1,
+ const char * pEnd1,
+ const char * pString2)
+{
+ DBG_ASSERT(pBegin1 && pBegin1 <= pEnd1 && pString2,
+ "equalIgnoreCase(): Bad sequences");
+
+ while (*pString2 != 0)
+ if (pBegin1 == pEnd1
+ || (rtl::toAsciiUpperCase(static_cast<unsigned char>(*pBegin1++))
+ != rtl::toAsciiUpperCase(
+ static_cast<unsigned char>(*pString2++))))
+ return false;
+ return pBegin1 == pEnd1;
+}
+
+struct EncodingEntry
+{
+ char const * m_aName;
+ rtl_TextEncoding m_eEncoding;
+};
+
+// The source for the following table is <ftp://ftp.iana.org/in-notes/iana/
+// assignments/character-sets> as of Jan, 21 2000 12:46:00, unless otherwise
+// noted:
+EncodingEntry const aEncodingMap[]
+ = { { "US-ASCII", RTL_TEXTENCODING_ASCII_US },
+ { "ANSI_X3.4-1968", RTL_TEXTENCODING_ASCII_US },
+ { "ISO-IR-6", RTL_TEXTENCODING_ASCII_US },
+ { "ANSI_X3.4-1986", RTL_TEXTENCODING_ASCII_US },
+ { "ISO_646.IRV:1991", RTL_TEXTENCODING_ASCII_US },
+ { "ASCII", RTL_TEXTENCODING_ASCII_US },
+ { "ISO646-US", RTL_TEXTENCODING_ASCII_US },
+ { "US", RTL_TEXTENCODING_ASCII_US },
+ { "IBM367", RTL_TEXTENCODING_ASCII_US },
+ { "CP367", RTL_TEXTENCODING_ASCII_US },
+ { "CSASCII", RTL_TEXTENCODING_ASCII_US },
+ { "ISO-8859-1", RTL_TEXTENCODING_ISO_8859_1 },
+ { "ISO_8859-1:1987", RTL_TEXTENCODING_ISO_8859_1 },
+ { "ISO-IR-100", RTL_TEXTENCODING_ISO_8859_1 },
+ { "ISO_8859-1", RTL_TEXTENCODING_ISO_8859_1 },
+ { "LATIN1", RTL_TEXTENCODING_ISO_8859_1 },
+ { "L1", RTL_TEXTENCODING_ISO_8859_1 },
+ { "IBM819", RTL_TEXTENCODING_ISO_8859_1 },
+ { "CP819", RTL_TEXTENCODING_ISO_8859_1 },
+ { "CSISOLATIN1", RTL_TEXTENCODING_ISO_8859_1 },
+ { "ISO-8859-2", RTL_TEXTENCODING_ISO_8859_2 },
+ { "ISO_8859-2:1987", RTL_TEXTENCODING_ISO_8859_2 },
+ { "ISO-IR-101", RTL_TEXTENCODING_ISO_8859_2 },
+ { "ISO_8859-2", RTL_TEXTENCODING_ISO_8859_2 },
+ { "LATIN2", RTL_TEXTENCODING_ISO_8859_2 },
+ { "L2", RTL_TEXTENCODING_ISO_8859_2 },
+ { "CSISOLATIN2", RTL_TEXTENCODING_ISO_8859_2 },
+ { "ISO-8859-3", RTL_TEXTENCODING_ISO_8859_3 },
+ { "ISO_8859-3:1988", RTL_TEXTENCODING_ISO_8859_3 },
+ { "ISO-IR-109", RTL_TEXTENCODING_ISO_8859_3 },
+ { "ISO_8859-3", RTL_TEXTENCODING_ISO_8859_3 },
+ { "LATIN3", RTL_TEXTENCODING_ISO_8859_3 },
+ { "L3", RTL_TEXTENCODING_ISO_8859_3 },
+ { "CSISOLATIN3", RTL_TEXTENCODING_ISO_8859_3 },
+ { "ISO-8859-4", RTL_TEXTENCODING_ISO_8859_4 },
+ { "ISO_8859-4:1988", RTL_TEXTENCODING_ISO_8859_4 },
+ { "ISO-IR-110", RTL_TEXTENCODING_ISO_8859_4 },
+ { "ISO_8859-4", RTL_TEXTENCODING_ISO_8859_4 },
+ { "LATIN4", RTL_TEXTENCODING_ISO_8859_4 },
+ { "L4", RTL_TEXTENCODING_ISO_8859_4 },
+ { "CSISOLATIN4", RTL_TEXTENCODING_ISO_8859_4 },
+ { "ISO-8859-5", RTL_TEXTENCODING_ISO_8859_5 },
+ { "ISO_8859-5:1988", RTL_TEXTENCODING_ISO_8859_5 },
+ { "ISO-IR-144", RTL_TEXTENCODING_ISO_8859_5 },
+ { "ISO_8859-5", RTL_TEXTENCODING_ISO_8859_5 },
+ { "CYRILLIC", RTL_TEXTENCODING_ISO_8859_5 },
+ { "CSISOLATINCYRILLIC", RTL_TEXTENCODING_ISO_8859_5 },
+ { "ISO-8859-6", RTL_TEXTENCODING_ISO_8859_6 },
+ { "ISO_8859-6:1987", RTL_TEXTENCODING_ISO_8859_6 },
+ { "ISO-IR-127", RTL_TEXTENCODING_ISO_8859_6 },
+ { "ISO_8859-6", RTL_TEXTENCODING_ISO_8859_6 },
+ { "ECMA-114", RTL_TEXTENCODING_ISO_8859_6 },
+ { "ASMO-708", RTL_TEXTENCODING_ISO_8859_6 },
+ { "ARABIC", RTL_TEXTENCODING_ISO_8859_6 },
+ { "CSISOLATINARABIC", RTL_TEXTENCODING_ISO_8859_6 },
+ { "ISO-8859-7", RTL_TEXTENCODING_ISO_8859_7 },
+ { "ISO_8859-7:1987", RTL_TEXTENCODING_ISO_8859_7 },
+ { "ISO-IR-126", RTL_TEXTENCODING_ISO_8859_7 },
+ { "ISO_8859-7", RTL_TEXTENCODING_ISO_8859_7 },
+ { "ELOT_928", RTL_TEXTENCODING_ISO_8859_7 },
+ { "ECMA-118", RTL_TEXTENCODING_ISO_8859_7 },
+ { "GREEK", RTL_TEXTENCODING_ISO_8859_7 },
+ { "GREEK8", RTL_TEXTENCODING_ISO_8859_7 },
+ { "CSISOLATINGREEK", RTL_TEXTENCODING_ISO_8859_7 },
+ { "ISO-8859-8", RTL_TEXTENCODING_ISO_8859_8 },
+ { "ISO_8859-8:1988", RTL_TEXTENCODING_ISO_8859_8 },
+ { "ISO-IR-138", RTL_TEXTENCODING_ISO_8859_8 },
+ { "ISO_8859-8", RTL_TEXTENCODING_ISO_8859_8 },
+ { "HEBREW", RTL_TEXTENCODING_ISO_8859_8 },
+ { "CSISOLATINHEBREW", RTL_TEXTENCODING_ISO_8859_8 },
+ { "ISO-8859-9", RTL_TEXTENCODING_ISO_8859_9 },
+ { "ISO_8859-9:1989", RTL_TEXTENCODING_ISO_8859_9 },
+ { "ISO-IR-148", RTL_TEXTENCODING_ISO_8859_9 },
+ { "ISO_8859-9", RTL_TEXTENCODING_ISO_8859_9 },
+ { "LATIN5", RTL_TEXTENCODING_ISO_8859_9 },
+ { "L5", RTL_TEXTENCODING_ISO_8859_9 },
+ { "CSISOLATIN5", RTL_TEXTENCODING_ISO_8859_9 },
+ { "ISO-8859-14", RTL_TEXTENCODING_ISO_8859_14 }, // RFC 2047
+ { "ISO_8859-15", RTL_TEXTENCODING_ISO_8859_15 },
+ { "ISO-8859-15", RTL_TEXTENCODING_ISO_8859_15 }, // RFC 2047
+ { "MACINTOSH", RTL_TEXTENCODING_APPLE_ROMAN },
+ { "MAC", RTL_TEXTENCODING_APPLE_ROMAN },
+ { "CSMACINTOSH", RTL_TEXTENCODING_APPLE_ROMAN },
+ { "IBM437", RTL_TEXTENCODING_IBM_437 },
+ { "CP437", RTL_TEXTENCODING_IBM_437 },
+ { "437", RTL_TEXTENCODING_IBM_437 },
+ { "CSPC8CODEPAGE437", RTL_TEXTENCODING_IBM_437 },
+ { "IBM850", RTL_TEXTENCODING_IBM_850 },
+ { "CP850", RTL_TEXTENCODING_IBM_850 },
+ { "850", RTL_TEXTENCODING_IBM_850 },
+ { "CSPC850MULTILINGUAL", RTL_TEXTENCODING_IBM_850 },
+ { "IBM860", RTL_TEXTENCODING_IBM_860 },
+ { "CP860", RTL_TEXTENCODING_IBM_860 },
+ { "860", RTL_TEXTENCODING_IBM_860 },
+ { "CSIBM860", RTL_TEXTENCODING_IBM_860 },
+ { "IBM861", RTL_TEXTENCODING_IBM_861 },
+ { "CP861", RTL_TEXTENCODING_IBM_861 },
+ { "861", RTL_TEXTENCODING_IBM_861 },
+ { "CP-IS", RTL_TEXTENCODING_IBM_861 },
+ { "CSIBM861", RTL_TEXTENCODING_IBM_861 },
+ { "IBM863", RTL_TEXTENCODING_IBM_863 },
+ { "CP863", RTL_TEXTENCODING_IBM_863 },
+ { "863", RTL_TEXTENCODING_IBM_863 },
+ { "CSIBM863", RTL_TEXTENCODING_IBM_863 },
+ { "IBM865", RTL_TEXTENCODING_IBM_865 },
+ { "CP865", RTL_TEXTENCODING_IBM_865 },
+ { "865", RTL_TEXTENCODING_IBM_865 },
+ { "CSIBM865", RTL_TEXTENCODING_IBM_865 },
+ { "IBM775", RTL_TEXTENCODING_IBM_775 },
+ { "CP775", RTL_TEXTENCODING_IBM_775 },
+ { "CSPC775BALTIC", RTL_TEXTENCODING_IBM_775 },
+ { "IBM852", RTL_TEXTENCODING_IBM_852 },
+ { "CP852", RTL_TEXTENCODING_IBM_852 },
+ { "852", RTL_TEXTENCODING_IBM_852 },
+ { "CSPCP852", RTL_TEXTENCODING_IBM_852 },
+ { "IBM855", RTL_TEXTENCODING_IBM_855 },
+ { "CP855", RTL_TEXTENCODING_IBM_855 },
+ { "855", RTL_TEXTENCODING_IBM_855 },
+ { "CSIBM855", RTL_TEXTENCODING_IBM_855 },
+ { "IBM857", RTL_TEXTENCODING_IBM_857 },
+ { "CP857", RTL_TEXTENCODING_IBM_857 },
+ { "857", RTL_TEXTENCODING_IBM_857 },
+ { "CSIBM857", RTL_TEXTENCODING_IBM_857 },
+ { "IBM862", RTL_TEXTENCODING_IBM_862 },
+ { "CP862", RTL_TEXTENCODING_IBM_862 },
+ { "862", RTL_TEXTENCODING_IBM_862 },
+ { "CSPC862LATINHEBREW", RTL_TEXTENCODING_IBM_862 },
+ { "IBM864", RTL_TEXTENCODING_IBM_864 },
+ { "CP864", RTL_TEXTENCODING_IBM_864 },
+ { "CSIBM864", RTL_TEXTENCODING_IBM_864 },
+ { "IBM866", RTL_TEXTENCODING_IBM_866 },
+ { "CP866", RTL_TEXTENCODING_IBM_866 },
+ { "866", RTL_TEXTENCODING_IBM_866 },
+ { "CSIBM866", RTL_TEXTENCODING_IBM_866 },
+ { "IBM869", RTL_TEXTENCODING_IBM_869 },
+ { "CP869", RTL_TEXTENCODING_IBM_869 },
+ { "869", RTL_TEXTENCODING_IBM_869 },
+ { "CP-GR", RTL_TEXTENCODING_IBM_869 },
+ { "CSIBM869", RTL_TEXTENCODING_IBM_869 },
+ { "WINDOWS-1250", RTL_TEXTENCODING_MS_1250 },
+ { "WINDOWS-1251", RTL_TEXTENCODING_MS_1251 },
+ { "WINDOWS-1253", RTL_TEXTENCODING_MS_1253 },
+ { "WINDOWS-1254", RTL_TEXTENCODING_MS_1254 },
+ { "WINDOWS-1255", RTL_TEXTENCODING_MS_1255 },
+ { "WINDOWS-1256", RTL_TEXTENCODING_MS_1256 },
+ { "WINDOWS-1257", RTL_TEXTENCODING_MS_1257 },
+ { "WINDOWS-1258", RTL_TEXTENCODING_MS_1258 },
+ { "SHIFT_JIS", RTL_TEXTENCODING_SHIFT_JIS },
+ { "MS_KANJI", RTL_TEXTENCODING_SHIFT_JIS },
+ { "CSSHIFTJIS", RTL_TEXTENCODING_SHIFT_JIS },
+ { "GB2312", RTL_TEXTENCODING_GB_2312 },
+ { "CSGB2312", RTL_TEXTENCODING_GB_2312 },
+ { "BIG5", RTL_TEXTENCODING_BIG5 },
+ { "CSBIG5", RTL_TEXTENCODING_BIG5 },
+ { "EUC-JP", RTL_TEXTENCODING_EUC_JP },
+ { "EXTENDED_UNIX_CODE_PACKED_FORMAT_FOR_JAPANESE",
+ RTL_TEXTENCODING_EUC_JP },
+ { "CSEUCPKDFMTJAPANESE", RTL_TEXTENCODING_EUC_JP },
+ { "ISO-2022-JP", RTL_TEXTENCODING_ISO_2022_JP },
+ { "CSISO2022JP", RTL_TEXTENCODING_ISO_2022_JP },
+ { "ISO-2022-CN", RTL_TEXTENCODING_ISO_2022_CN },
+ { "KOI8-R", RTL_TEXTENCODING_KOI8_R },
+ { "CSKOI8R", RTL_TEXTENCODING_KOI8_R },
+ { "UTF-7", RTL_TEXTENCODING_UTF7 },
+ { "UTF-8", RTL_TEXTENCODING_UTF8 },
+ { "ISO-8859-10", RTL_TEXTENCODING_ISO_8859_10 }, // RFC 2047
+ { "ISO-8859-13", RTL_TEXTENCODING_ISO_8859_13 }, // RFC 2047
+ { "EUC-KR", RTL_TEXTENCODING_EUC_KR },
+ { "CSEUCKR", RTL_TEXTENCODING_EUC_KR },
+ { "ISO-2022-KR", RTL_TEXTENCODING_ISO_2022_KR },
+ { "CSISO2022KR", RTL_TEXTENCODING_ISO_2022_KR },
+ { "ISO-10646-UCS-4", RTL_TEXTENCODING_UCS4 },
+ { "CSUCS4", RTL_TEXTENCODING_UCS4 },
+ { "ISO-10646-UCS-2", RTL_TEXTENCODING_UCS2 },
+ { "CSUNICODE", RTL_TEXTENCODING_UCS2 } };
+
+rtl_TextEncoding getCharsetEncoding(char const * pBegin,
+ char const * pEnd)
+{
+ for (const EncodingEntry& i : aEncodingMap)
+ if (equalIgnoreCase(pBegin, pEnd, i.m_aName))
+ return i.m_eEncoding;
+ return RTL_TEXTENCODING_DONTKNOW;
+}
+
+}
+
+// INetMIME
+
+// static
+bool INetMIME::isAtomChar(sal_uInt32 nChar)
+{
+ static const bool aMap[128]
+ = { false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, true, false, true, true, true, true, true, // !"#$%&'
+ false, false, true, true, false, true, false, true, //()*+,-./
+ true, true, true, true, true, true, true, true, //01234567
+ true, true, false, false, false, true, false, true, //89:;<=>?
+ false, true, true, true, true, true, true, true, //@ABCDEFG
+ true, true, true, true, true, true, true, true, //HIJKLMNO
+ true, true, true, true, true, true, true, true, //PQRSTUVW
+ true, true, true, false, false, false, true, true, //XYZ[\]^_
+ true, true, true, true, true, true, true, true, //`abcdefg
+ true, true, true, true, true, true, true, true, //hijklmno
+ true, true, true, true, true, true, true, true, //pqrstuvw
+ true, true, true, true, true, true, true, false //xyz{|}~
+ };
+ return rtl::isAscii(nChar) && aMap[nChar];
+}
+
+// static
+bool INetMIME::isIMAPAtomChar(sal_uInt32 nChar)
+{
+ static const bool aMap[128]
+ = { false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, true, false, true, true, false, true, true, // !"#$%&'
+ false, false, false, true, true, true, true, true, //()*+,-./
+ true, true, true, true, true, true, true, true, //01234567
+ true, true, true, true, true, true, true, true, //89:;<=>?
+ true, true, true, true, true, true, true, true, //@ABCDEFG
+ true, true, true, true, true, true, true, true, //HIJKLMNO
+ true, true, true, true, true, true, true, true, //PQRSTUVW
+ true, true, true, true, false, true, true, true, //XYZ[\]^_
+ true, true, true, true, true, true, true, true, //`abcdefg
+ true, true, true, true, true, true, true, true, //hijklmno
+ true, true, true, true, true, true, true, true, //pqrstuvw
+ true, true, true, false, true, true, true, false //xyz{|}~
+ };
+ return rtl::isAscii(nChar) && aMap[nChar];
+}
+
+// static
+bool INetMIME::equalIgnoreCase(const sal_Unicode * pBegin1,
+ const sal_Unicode * pEnd1,
+ const char * pString2)
+{
+ DBG_ASSERT(pBegin1 && pBegin1 <= pEnd1 && pString2,
+ "INetMIME::equalIgnoreCase(): Bad sequences");
+
+ while (*pString2 != 0)
+ if (pBegin1 == pEnd1
+ || (rtl::toAsciiUpperCase(*pBegin1++)
+ != rtl::toAsciiUpperCase(
+ static_cast<unsigned char>(*pString2++))))
+ return false;
+ return pBegin1 == pEnd1;
+}
+
+// static
+bool INetMIME::scanUnsigned(const sal_Unicode *& rBegin,
+ const sal_Unicode * pEnd, bool bLeadingZeroes,
+ sal_uInt32 & rValue)
+{
+ sal_uInt64 nTheValue = 0;
+ const sal_Unicode * p = rBegin;
+ for ( ; p != pEnd; ++p)
+ {
+ int nWeight = getWeight(*p);
+ if (nWeight < 0)
+ break;
+ nTheValue = 10 * nTheValue + nWeight;
+ if (nTheValue > std::numeric_limits< sal_uInt32 >::max())
+ return false;
+ }
+ if (nTheValue == 0 && (p == rBegin || (!bLeadingZeroes && p - rBegin != 1)))
+ return false;
+ rBegin = p;
+ rValue = sal_uInt32(nTheValue);
+ return true;
+}
+
+// static
+sal_Unicode const * INetMIME::scanContentType(
+ std::u16string_view rStr, OUString * pType,
+ OUString * pSubType, INetContentTypeParameterList * pParameters)
+{
+ sal_Unicode const * pBegin = rStr.data();
+ sal_Unicode const * pEnd = pBegin + rStr.size();
+ sal_Unicode const * p = skipLinearWhiteSpaceComment(pBegin, pEnd);
+ sal_Unicode const * pTypeBegin = p;
+ while (p != pEnd && isTokenChar(*p))
+ {
+ ++p;
+ }
+ if (p == pTypeBegin)
+ return nullptr;
+ sal_Unicode const * pTypeEnd = p;
+
+ p = skipLinearWhiteSpaceComment(p, pEnd);
+ if (p == pEnd || *p++ != '/')
+ return nullptr;
+
+ p = skipLinearWhiteSpaceComment(p, pEnd);
+ sal_Unicode const * pSubTypeBegin = p;
+ while (p != pEnd && isTokenChar(*p))
+ {
+ ++p;
+ }
+ if (p == pSubTypeBegin)
+ return nullptr;
+ sal_Unicode const * pSubTypeEnd = p;
+
+ if (pType != nullptr)
+ {
+ *pType = OUString(pTypeBegin, pTypeEnd - pTypeBegin).toAsciiLowerCase();
+ }
+ if (pSubType != nullptr)
+ {
+ *pSubType = OUString(pSubTypeBegin, pSubTypeEnd - pSubTypeBegin)
+ .toAsciiLowerCase();
+ }
+
+ return scanParameters(p, pEnd, pParameters);
+}
+
+// static
+OUString INetMIME::decodeHeaderFieldBody(const OString& rBody)
+{
+ // Due to a bug in INetCoreRFC822MessageStream::ConvertTo7Bit(), old
+ // versions of StarOffice send mails with header fields where encoded
+ // words can be preceded by '=', ',', '.', '"', or '(', and followed by
+ // '=', ',', '.', '"', ')', without any required white space in between.
+ // And there appear to exist some broken mailers that only encode single
+ // letters within words, like "Appel
+ // =?iso-8859-1?Q?=E0?=t=?iso-8859-1?Q?=E9?=moin", so it seems best to
+ // detect encoded words even when not properly surrounded by white space.
+
+ // Non US-ASCII characters in rBody are treated as ISO-8859-1.
+
+ // encoded-word = "=?"
+ // 1*(%x21 / %x23-27 / %x2A-2B / %x2D / %30-39 / %x41-5A / %x5E-7E)
+ // ["*" 1*8ALPHA *("-" 1*8ALPHA)] "?"
+ // ("B?" *(4base64) (4base64 / 3base64 "=" / 2base64 "==")
+ // / "Q?" 1*(%x21-3C / %x3E / %x40-7E / "=" 2HEXDIG))
+ // "?="
+
+ // base64 = ALPHA / DIGIT / "+" / "/"
+
+ const char * pBegin = rBody.getStr();
+ const char * pEnd = pBegin + rBody.getLength();
+
+ OUStringBuffer sDecoded;
+ const char * pCopyBegin = pBegin;
+
+ /* bool bStartEncodedWord = true; */
+ const char * pWSPBegin = pBegin;
+
+ for (const char * p = pBegin; p != pEnd;)
+ {
+ if (*p == '=' /* && bStartEncodedWord */)
+ {
+ const char * q = p + 1;
+ bool bEncodedWord = q != pEnd && *q++ == '?';
+
+ rtl_TextEncoding eCharsetEncoding = RTL_TEXTENCODING_DONTKNOW;
+ if (bEncodedWord)
+ {
+ const char * pCharsetBegin = q;
+ const char * pLanguageBegin = nullptr;
+ int nAlphaCount = 0;
+ for (bool bDone = false; !bDone;)
+ if (q == pEnd)
+ {
+ bEncodedWord = false;
+ bDone = true;
+ }
+ else
+ {
+ char cChar = *q++;
+ switch (cChar)
+ {
+ case '*':
+ pLanguageBegin = q - 1;
+ nAlphaCount = 0;
+ break;
+
+ case '-':
+ if (pLanguageBegin != nullptr)
+ {
+ if (nAlphaCount == 0)
+ pLanguageBegin = nullptr;
+ else
+ nAlphaCount = 0;
+ }
+ break;
+
+ case '?':
+ if (pCharsetBegin == q - 1)
+ bEncodedWord = false;
+ else
+ {
+ eCharsetEncoding
+ = getCharsetEncoding(
+ pCharsetBegin,
+ pLanguageBegin == nullptr
+ || nAlphaCount == 0 ?
+ q - 1 : pLanguageBegin);
+ bEncodedWord = isMIMECharsetEncoding(
+ eCharsetEncoding);
+ eCharsetEncoding
+ = translateFromMIME(eCharsetEncoding);
+ }
+ bDone = true;
+ break;
+
+ default:
+ if (pLanguageBegin != nullptr
+ && (!rtl::isAsciiAlpha(
+ static_cast<unsigned char>(cChar))
+ || ++nAlphaCount > 8))
+ pLanguageBegin = nullptr;
+ break;
+ }
+ }
+ }
+
+ bool bEncodingB = false;
+ if (bEncodedWord)
+ {
+ if (q == pEnd)
+ bEncodedWord = false;
+ else
+ {
+ switch (*q++)
+ {
+ case 'B':
+ case 'b':
+ bEncodingB = true;
+ break;
+
+ case 'Q':
+ case 'q':
+ bEncodingB = false;
+ break;
+
+ default:
+ bEncodedWord = false;
+ break;
+ }
+ }
+ }
+
+ bEncodedWord = bEncodedWord && q != pEnd && *q++ == '?';
+
+ OStringBuffer sText;
+ if (bEncodedWord)
+ {
+ if (bEncodingB)
+ {
+ for (bool bDone = false; !bDone;)
+ {
+ if (pEnd - q < 4)
+ {
+ bEncodedWord = false;
+ bDone = true;
+ }
+ else
+ {
+ bool bFinal = false;
+ int nCount = 3;
+ sal_uInt32 nValue = 0;
+ for (int nShift = 18; nShift >= 0; nShift -= 6)
+ {
+ int nWeight = getBase64Weight(*q++);
+ if (nWeight == -2)
+ {
+ bEncodedWord = false;
+ bDone = true;
+ break;
+ }
+ if (nWeight == -1)
+ {
+ if (!bFinal)
+ {
+ if (nShift >= 12)
+ {
+ bEncodedWord = false;
+ bDone = true;
+ break;
+ }
+ bFinal = true;
+ nCount = nShift == 6 ? 1 : 2;
+ }
+ }
+ else
+ nValue |= nWeight << nShift;
+ }
+ if (bEncodedWord)
+ {
+ for (int nShift = 16; nCount-- > 0; nShift -= 8)
+ sText.append(char(nValue >> nShift & 0xFF));
+ if (*q == '?')
+ {
+ ++q;
+ bDone = true;
+ }
+ if (bFinal && !bDone)
+ {
+ bEncodedWord = false;
+ bDone = true;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ const char * pEncodedTextBegin = q;
+ const char * pEncodedTextCopyBegin = q;
+ for (bool bDone = false; !bDone;)
+ if (q == pEnd)
+ {
+ bEncodedWord = false;
+ bDone = true;
+ }
+ else
+ {
+ sal_uInt32 nChar = static_cast<unsigned char>(*q++);
+ switch (nChar)
+ {
+ case '=':
+ {
+ if (pEnd - q < 2)
+ {
+ bEncodedWord = false;
+ bDone = true;
+ break;
+ }
+ int nDigit1 = getHexWeight(q[0]);
+ int nDigit2 = getHexWeight(q[1]);
+ if (nDigit1 < 0 || nDigit2 < 0)
+ {
+ bEncodedWord = false;
+ bDone = true;
+ break;
+ }
+ sText.append(
+ rBody.subView(
+ (pEncodedTextCopyBegin - pBegin),
+ (q - 1 - pEncodedTextCopyBegin))
+ + OStringChar(char(nDigit1 << 4 | nDigit2)));
+ q += 2;
+ pEncodedTextCopyBegin = q;
+ break;
+ }
+
+ case '?':
+ if (q - pEncodedTextBegin > 1)
+ sText.append(rBody.subView(
+ (pEncodedTextCopyBegin - pBegin),
+ (q - 1 - pEncodedTextCopyBegin)));
+ else
+ bEncodedWord = false;
+ bDone = true;
+ break;
+
+ case '_':
+ sText.append(
+ rBody.subView(
+ (pEncodedTextCopyBegin - pBegin),
+ (q - 1 - pEncodedTextCopyBegin))
+ + OString::Concat(" "));
+ pEncodedTextCopyBegin = q;
+ break;
+
+ default:
+ if (!isVisible(nChar))
+ {
+ bEncodedWord = false;
+ bDone = true;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ bEncodedWord = bEncodedWord && q != pEnd && *q++ == '=';
+
+ std::unique_ptr<sal_Unicode[]> pUnicodeBuffer;
+ sal_Size nUnicodeSize = 0;
+ if (bEncodedWord)
+ {
+ pUnicodeBuffer
+ = convertToUnicode(sText.getStr(),
+ sText.getStr() + sText.getLength(),
+ eCharsetEncoding, nUnicodeSize);
+ if (!pUnicodeBuffer)
+ bEncodedWord = false;
+ }
+
+ if (bEncodedWord)
+ {
+ appendISO88591(sDecoded, pCopyBegin, pWSPBegin);
+ sDecoded.append(
+ pUnicodeBuffer.get(),
+ static_cast< sal_Int32 >(nUnicodeSize));
+ pUnicodeBuffer.reset();
+ p = q;
+ pCopyBegin = p;
+
+ pWSPBegin = p;
+ while (p != pEnd && isWhiteSpace(*p))
+ ++p;
+ /* bStartEncodedWord = p != pWSPBegin; */
+ continue;
+ }
+ }
+
+ if (p == pEnd)
+ break;
+
+ switch (*p++)
+ {
+ case '"':
+ /* bStartEncodedWord = true; */
+ break;
+
+ case '(':
+ /* bStartEncodedWord = true; */
+ break;
+
+ case ')':
+ /* bStartEncodedWord = false; */
+ break;
+
+ default:
+ {
+ const char * pUTF8Begin = p - 1;
+ const char * pUTF8End = pUTF8Begin;
+ sal_uInt32 nCharacter = 0;
+ if (translateUTF8Char(pUTF8End, pEnd, nCharacter))
+ {
+ appendISO88591(sDecoded, pCopyBegin, p - 1);
+ sDecoded.appendUtf32(nCharacter);
+ p = pUTF8End;
+ pCopyBegin = p;
+ }
+ /* bStartEncodedWord = false; */
+ break;
+ }
+ }
+ pWSPBegin = p;
+ }
+
+ appendISO88591(sDecoded, pCopyBegin, pEnd);
+ return sDecoded.makeStringAndClear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/inet/inetmsg.cxx b/tools/source/inet/inetmsg.cxx
new file mode 100644
index 0000000000..a5c6838176
--- /dev/null
+++ b/tools/source/inet/inetmsg.cxx
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/types.h>
+#include <tools/datetime.hxx>
+#include <tools/inetmsg.hxx>
+#include <comphelper/string.hxx>
+#include <rtl/character.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/sprintf.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <map>
+
+void INetMIMEMessage::SetHeaderField_Impl (
+ const OString &rName,
+ const OUString &rValue,
+ sal_uInt32 &rnIndex)
+{
+ SetHeaderField_Impl (
+ INetMessageHeader (rName, rValue.toUtf8()), rnIndex);
+}
+
+/* ParseDateField and local helper functions.
+ *
+ * Parses a String in (implied) GMT format into class Date and tools::Time objects.
+ * Four formats are accepted:
+ *
+ * [Wkd,] 1*2DIGIT Mon 2*4DIGIT 00:00:00 [GMT] (rfc1123)
+ * [Wkd,] 00 Mon 0000 00:00:00 [GMT]) (rfc822, rfc1123)
+ * Weekday, 00-Mon-00 00:00:00 [GMT] (rfc850, rfc1036)
+ * Wkd Mon 00 00:00:00 0000 [GMT] (ctime)
+ * 1*DIGIT (delta seconds)
+ */
+
+static const char *months[12] =
+{
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static sal_uInt16 ParseNumber(std::string_view rStr, size_t& nIndex)
+{
+ size_t n = nIndex;
+ while ((n < rStr.size())
+ && rtl::isAsciiDigit(static_cast<unsigned char>(rStr[n])))
+ n++;
+
+ std::string_view aNum(rStr.substr(nIndex, (n - nIndex)));
+ nIndex = n;
+
+ return static_cast<sal_uInt16>(o3tl::toInt32(aNum));
+}
+
+static sal_uInt16 ParseMonth(std::string_view rStr, size_t& nIndex)
+{
+ size_t n = nIndex;
+ while ((n < rStr.size())
+ && rtl::isAsciiAlpha(static_cast<unsigned char>(rStr[n])))
+ n++;
+
+ std::string_view aMonth(rStr.substr(nIndex, 3));
+ nIndex = n;
+
+ sal_uInt16 i;
+ for (i = 0; i < 12; i++)
+ if (o3tl::equalsIgnoreAsciiCase(aMonth, months[i])) break;
+ return (i + 1);
+}
+
+bool INetMIMEMessage::ParseDateField (
+ std::u16string_view rDateFieldW, DateTime& rDateTime)
+{
+ OString aDateField(OUStringToOString(rDateFieldW,
+ RTL_TEXTENCODING_ASCII_US));
+
+ if (aDateField.isEmpty()) return false;
+
+ if (aDateField.indexOf(':') != -1)
+ {
+ // Some DateTime format.
+ size_t nIndex = 0;
+
+ // Skip over <Wkd> or <Weekday>, leading and trailing space.
+ while ((nIndex < o3tl::make_unsigned(aDateField.getLength())) &&
+ (aDateField[nIndex] == ' '))
+ nIndex++;
+
+ while (
+ (nIndex < o3tl::make_unsigned(aDateField.getLength())) &&
+ (rtl::isAsciiAlpha (static_cast<unsigned char>(aDateField[nIndex])) ||
+ (aDateField[nIndex] == ',') ))
+ nIndex++;
+
+ while ((nIndex < o3tl::make_unsigned(aDateField.getLength())) &&
+ (aDateField[nIndex] == ' '))
+ nIndex++;
+
+ if (rtl::isAsciiAlpha (static_cast<unsigned char>(aDateField[nIndex])))
+ {
+ // Format: ctime().
+ if ((aDateField.getLength() - nIndex) < 20) return false;
+
+ rDateTime.SetMonth (ParseMonth (aDateField, nIndex)); nIndex++;
+ rDateTime.SetDay (ParseNumber (aDateField, nIndex)); nIndex++;
+
+ rDateTime.SetHour (ParseNumber (aDateField, nIndex)); nIndex++;
+ rDateTime.SetMin (ParseNumber (aDateField, nIndex)); nIndex++;
+ rDateTime.SetSec (ParseNumber (aDateField, nIndex)); nIndex++;
+ rDateTime.SetNanoSec (0);
+
+ sal_uInt16 nYear = ParseNumber (aDateField, nIndex);
+ if (nYear < 100) nYear += 1900;
+ rDateTime.SetYear (nYear);
+ }
+ else
+ {
+ // Format: RFC1036 or RFC1123.
+ if ((aDateField.getLength() - nIndex) < 17) return false;
+
+ rDateTime.SetDay (ParseNumber (aDateField, nIndex)); nIndex++;
+ rDateTime.SetMonth (ParseMonth (aDateField, nIndex)); nIndex++;
+
+ sal_uInt16 nYear = ParseNumber (aDateField, nIndex); nIndex++;
+ if (nYear < 100) nYear += 1900;
+ rDateTime.SetYear (nYear);
+
+ rDateTime.SetHour (ParseNumber (aDateField, nIndex)); nIndex++;
+ rDateTime.SetMin (ParseNumber (aDateField, nIndex)); nIndex++;
+ rDateTime.SetSec (ParseNumber (aDateField, nIndex)); nIndex++;
+ rDateTime.SetNanoSec (0);
+
+ const char cPossiblePlusMinus = nIndex < o3tl::make_unsigned(aDateField.getLength()) ? aDateField[nIndex] : 0;
+ if (cPossiblePlusMinus == '+' || cPossiblePlusMinus == '-')
+ {
+ // Offset from GMT: "(+|-)HHMM".
+ bool bEast = (aDateField[nIndex++] == '+');
+ sal_uInt16 nOffset = ParseNumber (aDateField, nIndex);
+ if (nOffset > 0)
+ {
+ tools::Time aDiff( tools::Time::EMPTY );
+ aDiff.SetHour (nOffset / 100);
+ aDiff.SetMin (nOffset % 100);
+ aDiff.SetSec (0);
+ aDiff.SetNanoSec (0);
+
+ if (bEast)
+ rDateTime -= aDiff;
+ else
+ rDateTime += aDiff;
+ }
+ }
+ }
+ }
+ else if (comphelper::string::isdigitAsciiString(aDateField))
+ {
+ // Format: delta seconds.
+ tools::Time aDelta (0);
+ aDelta.SetTime (aDateField.toInt32() * 100);
+
+ DateTime aNow( DateTime::SYSTEM );
+ aNow += aDelta;
+ aNow.ConvertToUTC();
+
+ rDateTime.SetDate (aNow.GetDate());
+ rDateTime.SetTime (aNow.GetTime());
+ }
+ else
+ {
+ // Junk.
+ return false;
+ }
+
+ return (rDateTime.IsValidAndGregorian() &&
+ !((rDateTime.GetSec() > 59) ||
+ (rDateTime.GetMin() > 59) ||
+ (rDateTime.GetHour() > 23) ));
+}
+
+const std::map<InetMessageMime, const char*> ImplINetMIMEMessageHeaderData =
+{
+ { InetMessageMime::VERSION, "MIME-Version"},
+ { InetMessageMime::CONTENT_DISPOSITION, "Content-Disposition"},
+ { InetMessageMime::CONTENT_TYPE, "Content-Type"},
+ { InetMessageMime::CONTENT_TRANSFER_ENCODING, "Content-Transfer-Encoding"}
+};
+
+INetMIMEMessage::INetMIMEMessage()
+ : pParent(nullptr)
+{
+ for (sal_uInt16 i = 0; i < static_cast<int>(InetMessageMime::NUMHDR); i++)
+ m_nMIMEIndex[static_cast<InetMessageMime>(i)] = SAL_MAX_UINT32;
+}
+
+INetMIMEMessage::~INetMIMEMessage()
+{
+}
+
+void INetMIMEMessage::SetMIMEVersion (const OUString& rVersion)
+{
+ SetHeaderField_Impl (
+ ImplINetMIMEMessageHeaderData.at(InetMessageMime::VERSION), rVersion,
+ m_nMIMEIndex[InetMessageMime::VERSION]);
+}
+
+void INetMIMEMessage::SetContentDisposition (const OUString& rDisposition)
+{
+ SetHeaderField_Impl (
+ ImplINetMIMEMessageHeaderData.at(InetMessageMime::CONTENT_DISPOSITION), rDisposition,
+ m_nMIMEIndex[InetMessageMime::CONTENT_DISPOSITION]);
+}
+
+void INetMIMEMessage::SetContentType (const OUString& rType)
+{
+ SetHeaderField_Impl (
+ ImplINetMIMEMessageHeaderData.at(InetMessageMime::CONTENT_TYPE), rType,
+ m_nMIMEIndex[InetMessageMime::CONTENT_TYPE]);
+}
+
+void INetMIMEMessage::SetContentTransferEncoding (
+ const OUString& rEncoding)
+{
+ SetHeaderField_Impl (
+ ImplINetMIMEMessageHeaderData.at(InetMessageMime::CONTENT_TRANSFER_ENCODING), rEncoding,
+ m_nMIMEIndex[InetMessageMime::CONTENT_TRANSFER_ENCODING]);
+}
+
+OUString INetMIMEMessage::GetDefaultContentType()
+{
+ if (pParent != nullptr)
+ {
+ OUString aParentCT (pParent->GetContentType());
+ if (aParentCT.isEmpty())
+ aParentCT = pParent->GetDefaultContentType();
+
+ if (aParentCT.equalsIgnoreAsciiCase("multipart/digest"))
+ return "message/rfc822";
+ }
+ return "text/plain; charset=us-ascii";
+}
+
+void INetMIMEMessage::EnableAttachMultipartFormDataChild()
+{
+ // Check context.
+ if (IsContainer())
+ return;
+
+ // Generate a unique boundary from current time.
+ char sTail[16 + 1];
+ tools::Time aCurTime( tools::Time::SYSTEM );
+ sal_uInt64 nThis = reinterpret_cast< sal_uIntPtr >( this ); // we can be on a 64bit architecture
+ nThis = ( ( nThis >> 32 ) ^ nThis ) & SAL_MAX_UINT32;
+ o3tl::sprintf (sTail, "%08X%08X",
+ static_cast< unsigned int >(aCurTime.GetTime()),
+ static_cast< unsigned int >(nThis));
+ m_aBoundary = "------------_4D48"_ostr;
+ m_aBoundary += sTail;
+
+ // Set header fields.
+ SetMIMEVersion("1.0");
+ SetContentType(
+ "multipart/form-data; boundary=" + OUString::fromUtf8(m_aBoundary));
+ SetContentTransferEncoding("7bit");
+}
+
+void INetMIMEMessage::AttachChild(std::unique_ptr<INetMIMEMessage> pChildMsg)
+{
+ assert(IsContainer());
+ if (IsContainer())
+ {
+ pChildMsg->pParent = this;
+ aChildren.push_back( std::move(pChildMsg) );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/inet/inetstrm.cxx b/tools/source/inet/inetstrm.cxx
new file mode 100644
index 0000000000..ac33f5eda4
--- /dev/null
+++ b/tools/source/inet/inetstrm.cxx
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cassert>
+
+#include <sal/types.h>
+#include <tools/inetmsg.hxx>
+#include <tools/inetstrm.hxx>
+
+int INetMIMEMessageStream::GetHeaderLine(char* pData, sal_uInt32 nSize)
+{
+ char* pWBuf = pData;
+
+ sal_uInt32 i, n;
+
+ if (maMsgBuffer.Tell() == 0)
+ {
+ // Insert formatted header into buffer.
+ n = pSourceMsg->GetHeaderCount();
+ for (i = 0; i < n; i++)
+ {
+ INetMessageHeader aHeader (pSourceMsg->GetHeaderField(i));
+ if (aHeader.GetValue().getLength())
+ {
+ // NYI: Folding long lines.
+ maMsgBuffer.WriteOString( aHeader.GetName() );
+ maMsgBuffer.WriteOString( ": " );
+ maMsgBuffer.WriteOString( aHeader.GetValue() );
+ maMsgBuffer.WriteOString( "\r\n" );
+ }
+ }
+
+ pMsgWrite = const_cast<char *>(static_cast<char const *>(maMsgBuffer.GetData()));
+ pMsgRead = pMsgWrite + maMsgBuffer.Tell();
+ }
+
+ n = pMsgRead - pMsgWrite;
+ if (n > 0)
+ {
+ // Move to caller.
+ if (nSize < n) n = nSize;
+ for (i = 0; i < n; i++) *pWBuf++ = *pMsgWrite++;
+ }
+ else
+ {
+ // Reset buffer.
+ maMsgBuffer.Seek(STREAM_SEEK_TO_BEGIN);
+ }
+
+ return (pWBuf - pData);
+}
+
+int INetMIMEMessageStream::GetBodyLine(char* pData, sal_uInt32 nSize)
+{
+ char* pWBuf = pData;
+ char* pWEnd = pData + nSize;
+
+ if (pSourceMsg->GetDocumentLB())
+ {
+ if (pMsgStrm == nullptr)
+ pMsgStrm.reset(new SvStream (pSourceMsg->GetDocumentLB()));
+
+ sal_uInt32 nRead = pMsgStrm->ReadBytes(pWBuf, (pWEnd - pWBuf));
+ pWBuf += nRead;
+ }
+
+ return (pWBuf - pData);
+}
+
+int INetMIMEMessageStream::GetMsgLine(char* pData, sal_uInt32 nSize)
+{
+ // Check for header or body.
+ if (!bHeaderGenerated)
+ {
+ if (!done)
+ {
+ // Prepare special header fields.
+ if (pSourceMsg->GetParent())
+ {
+ OUString aPCT(pSourceMsg->GetParent()->GetContentType());
+ if (aPCT.startsWithIgnoreAsciiCase("message/rfc822"))
+ pSourceMsg->SetMIMEVersion("1.0");
+ else
+ pSourceMsg->SetMIMEVersion(OUString());
+ }
+ else
+ {
+ pSourceMsg->SetMIMEVersion("1.0");
+ }
+
+ // Check ContentType.
+ OUString aContentType(pSourceMsg->GetContentType());
+ if (!aContentType.isEmpty())
+ {
+ // Determine default Content-Type.
+ OUString aDefaultType = pSourceMsg->GetDefaultContentType();
+
+ if (aDefaultType.equalsIgnoreAsciiCase(aContentType))
+ {
+ // No need to specify default.
+ pSourceMsg->SetContentType(OUString());
+ }
+ }
+
+ // No need to specify default.
+ pSourceMsg->SetContentTransferEncoding(OUString());
+
+ // Mark we're done.
+ done = true;
+ }
+
+ // Generate the message header.
+ int nRead = GetHeaderLine(pData, nSize);
+ if (nRead <= 0)
+ {
+ // Reset state.
+ done = false;
+ }
+ return nRead;
+ }
+ else
+ {
+ // Generate the message body.
+ if (pSourceMsg->IsContainer())
+ {
+ // Encapsulated message body.
+ while (!done)
+ {
+ if (pChildStrm == nullptr)
+ {
+ INetMIMEMessage *pChild = pSourceMsg->GetChild(nChildIndex);
+ if (pChild)
+ {
+ // Increment child index.
+ nChildIndex++;
+
+ // Create child stream.
+ pChildStrm.reset(new INetMIMEMessageStream(pChild, false));
+
+ if (pSourceMsg->IsMultipart())
+ {
+ // Insert multipart delimiter.
+ OString aDelim = "--" +
+ pSourceMsg->GetMultipartBoundary() +
+ "\r\n";
+
+ memcpy(pData, aDelim.getStr(),
+ aDelim.getLength());
+ return aDelim.getLength();
+ }
+ }
+ else
+ {
+ // No more parts. Mark we're done.
+ done = true;
+ nChildIndex = 0;
+
+ if (pSourceMsg->IsMultipart())
+ {
+ // Insert close delimiter.
+ OString aDelim = "--" +
+ pSourceMsg->GetMultipartBoundary() +
+ "--\r\n";
+
+ memcpy(pData, aDelim.getStr(),
+ aDelim.getLength());
+ return aDelim.getLength();
+ }
+ }
+ }
+ else
+ {
+ // Read current child stream.
+ int nRead = pChildStrm->Read(pData, nSize);
+ if (nRead > 0)
+ {
+ return nRead;
+ }
+ else
+ {
+ // Cleanup exhausted child stream.
+ pChildStrm.reset();
+ }
+ }
+ }
+ return 0;
+ }
+ else
+ {
+ // Single part message body.
+ if (pSourceMsg->GetDocumentLB() == nullptr)
+ {
+ // Empty message body.
+ return 0;
+ }
+
+ // No Encoding.
+ return GetBodyLine(pData, nSize);
+ }
+ }
+}
+
+namespace
+{
+
+const int BUFFER_SIZE = 2048;
+
+}
+
+INetMIMEMessageStream::INetMIMEMessageStream(
+ INetMIMEMessage *pMsg, bool headerGenerated):
+ pSourceMsg(pMsg),
+ bHeaderGenerated(headerGenerated),
+ mvBuffer(BUFFER_SIZE),
+ pMsgRead(nullptr),
+ pMsgWrite(nullptr),
+ done(false),
+ nChildIndex(0)
+{
+ assert(pMsg != nullptr);
+ maMsgBuffer.SetStreamCharSet(RTL_TEXTENCODING_ASCII_US);
+ pRead = pWrite = mvBuffer.data();
+}
+
+INetMIMEMessageStream::~INetMIMEMessageStream()
+{
+ pChildStrm.reset();
+}
+
+int INetMIMEMessageStream::Read(char* pData, sal_uInt32 nSize)
+{
+ char* pWBuf = pData;
+ char* pWEnd = pData + nSize;
+
+ while (pWBuf < pWEnd)
+ {
+ // Caller's buffer not yet filled.
+ sal_uInt32 n = pRead - pWrite;
+ if (n > 0)
+ {
+ // Bytes still in buffer.
+ sal_uInt32 m = pWEnd - pWBuf;
+ if (m < n) n = m;
+ for (sal_uInt32 i = 0; i < n; i++) *pWBuf++ = *pWrite++;
+ }
+ else
+ {
+ // Buffer empty. Reset to <Begin-of-Buffer>.
+ pRead = pWrite = mvBuffer.data();
+
+ // Read next message line.
+ int nRead = GetMsgLine(mvBuffer.data(), mvBuffer.size());
+ if (nRead > 0)
+ {
+ // Set read pointer.
+ pRead = mvBuffer.data() + nRead;
+ }
+ else
+ {
+ if (!bHeaderGenerated)
+ {
+ // Header generated. Insert empty line.
+ bHeaderGenerated = true;
+ *pRead++ = '\r';
+ *pRead++ = '\n';
+ }
+ else
+ {
+ // Body generated.
+ return (pWBuf - pData);
+ }
+ }
+ }
+ }
+ return (pWBuf - pData);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/memtools/multisel.cxx b/tools/source/memtools/multisel.cxx
new file mode 100644
index 0000000000..739d287487
--- /dev/null
+++ b/tools/source/memtools/multisel.cxx
@@ -0,0 +1,744 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cstddef>
+
+#include <o3tl/string_view.hxx>
+#include <tools/debug.hxx>
+#include <tools/multisel.hxx>
+
+#include <rtl/ustrbuf.hxx>
+
+void MultiSelection::ImplClear()
+{
+ // no selected indexes
+ nSelCount = 0;
+ aSels.clear();
+}
+
+std::size_t MultiSelection::ImplFindSubSelection( sal_Int32 nIndex ) const
+{
+ // iterate through the sub selections
+ std::size_t n = 0;
+ for ( ;
+ n < aSels.size() && nIndex > aSels[ n ].Max();
+ ++n ) {} /* empty loop */
+ return n;
+}
+
+void MultiSelection::ImplMergeSubSelections( sal_Int32 nPos1, std::size_t nPos2 )
+{
+ // didn't a sub selection at nPos2 exist?
+ if ( nPos2 >= aSels.size() )
+ return;
+
+ // did the sub selections touch each other?
+ if ( (aSels[ nPos1 ].Max() + 1) == aSels[ nPos2 ].Min() )
+ {
+ // merge them
+ aSels[ nPos1 ].Max() = aSels[ nPos2 ].Max();
+ aSels.erase( aSels.begin() + nPos2 );
+ }
+}
+
+MultiSelection::MultiSelection():
+ aTotRange( 0, -1 ),
+ nCurSubSel(0),
+ nCurIndex(0),
+ nSelCount(0),
+ bCurValid(false)
+{
+}
+
+void MultiSelection::Reset()
+{
+ aTotRange = Range(0, -1);
+ bCurValid = false;
+ // clear the old sub selections
+ ImplClear();
+}
+
+MultiSelection::MultiSelection( const MultiSelection& rOrig ) :
+ aTotRange(rOrig.aTotRange),
+ nSelCount(rOrig.nSelCount),
+ bCurValid(rOrig.bCurValid)
+{
+ if ( bCurValid )
+ {
+ nCurSubSel = rOrig.nCurSubSel;
+ nCurIndex = rOrig.nCurIndex;
+ }
+ else
+ {
+ nCurSubSel = 0;
+ nCurIndex = 0;
+ }
+
+ // copy the sub selections
+ aSels.insert( aSels.end(), rOrig.aSels.begin(), rOrig.aSels.end() );
+}
+
+MultiSelection::MultiSelection( const Range& rRange ):
+ aTotRange(rRange),
+ nCurSubSel(0),
+ nCurIndex(0),
+ nSelCount(0),
+ bCurValid(false)
+{
+}
+
+MultiSelection::~MultiSelection()
+{
+}
+
+MultiSelection& MultiSelection::operator= ( const MultiSelection& rOrig )
+{
+ aTotRange = rOrig.aTotRange;
+ bCurValid = rOrig.bCurValid;
+ if ( bCurValid )
+ {
+ nCurSubSel = rOrig.nCurSubSel;
+ nCurIndex = rOrig.nCurIndex;
+ }
+
+ // clear the old and copy the sub selections
+ ImplClear();
+ aSels.insert( aSels.end(), rOrig.aSels.begin(), rOrig.aSels.end() );
+ nSelCount = rOrig.nSelCount;
+
+ return *this;
+}
+
+void MultiSelection::SelectAll( bool bSelect )
+{
+ ImplClear();
+ if ( bSelect )
+ {
+ aSels.push_back( aTotRange );
+ nSelCount = aTotRange.Len();
+ }
+}
+
+bool MultiSelection::Select( sal_Int32 nIndex, bool bSelect )
+{
+ DBG_ASSERT( aTotRange.Contains(nIndex), "selected index out of range" );
+
+ // out of range?
+ if ( !aTotRange.Contains(nIndex) )
+ return false;
+
+ // find the virtual target position
+ std::size_t nSubSelPos = ImplFindSubSelection( nIndex );
+
+ if ( bSelect )
+ {
+ // is it included in the found sub selection?
+ if ( nSubSelPos < aSels.size() && aSels[ nSubSelPos ].Contains( nIndex ) )
+ // already selected, nothing to do
+ return false;
+
+ // it will become selected
+ ++nSelCount;
+
+ // is it at the end of the previous sub selection
+ if ( nSubSelPos > 0 &&
+ aSels[ nSubSelPos-1 ].Max() == (nIndex-1) )
+ {
+ // expand the previous sub selection
+ aSels[ nSubSelPos-1 ].Max() = nIndex;
+
+ // try to merge the previous sub selection
+ ImplMergeSubSelections( nSubSelPos-1, nSubSelPos );
+ }
+ // is it at the beginning of the found sub selection
+ else if ( nSubSelPos < aSels.size()
+ && aSels[ nSubSelPos ].Min() == (nIndex+1)
+ )
+ // expand the found sub selection
+ aSels[ nSubSelPos ].Min() = nIndex;
+ else
+ {
+ // create a new sub selection
+ if ( nSubSelPos < aSels.size() ) {
+ aSels.insert( aSels.begin() + nSubSelPos, Range( nIndex, nIndex ) );
+ } else {
+ aSels.push_back( Range( nIndex, nIndex ) );
+ }
+ if ( bCurValid && nCurSubSel >= nSubSelPos )
+ ++nCurSubSel;
+ }
+ }
+ else
+ {
+ // is it excluded from the found sub selection?
+ if ( nSubSelPos >= aSels.size()
+ || !aSels[ nSubSelPos ].Contains( nIndex )
+ ) {
+ // not selected, nothing to do
+ return false;
+ }
+
+ // it will become deselected
+ --nSelCount;
+
+ // is it the only index in the found sub selection?
+ if ( aSels[ nSubSelPos ].Len() == 1 )
+ {
+ // remove the complete sub selection
+ aSels.erase( aSels.begin() + nSubSelPos );
+ return true;
+ }
+
+ // is it at the beginning of the found sub selection?
+ if ( aSels[ nSubSelPos ].Min() == nIndex )
+ ++aSels[ nSubSelPos ].Min();
+ // is it at the end of the found sub selection?
+ else if ( aSels[ nSubSelPos ].Max() == nIndex )
+ --aSels[ nSubSelPos ].Max();
+ // it is in the middle of the found sub selection?
+ else
+ {
+ // split the sub selection
+ if ( nSubSelPos < aSels.size() ) {
+ aSels.insert( aSels.begin() + nSubSelPos, Range( aSels[ nSubSelPos ].Min(), nIndex-1 ) );
+ } else {
+ aSels.push_back( Range( aSels[ nSubSelPos ].Min(), nIndex-1 ) );
+ }
+ aSels[ nSubSelPos+1 ].Min() = nIndex + 1;
+ }
+ }
+
+ return true;
+}
+
+void MultiSelection::Select( const Range& rIndexRange, bool bSelect )
+{
+ sal_Int32 nOld;
+
+ sal_Int32 nTmpMin = rIndexRange.Min();
+ sal_Int32 nTmpMax = rIndexRange.Max();
+ sal_Int32 nCurMin = FirstSelected();
+ sal_Int32 nCurMax = LastSelected();
+ DBG_ASSERT(aTotRange.Contains(nTmpMax), "selected index out of range" );
+ DBG_ASSERT(aTotRange.Contains(nTmpMin), "selected index out of range" );
+
+ // replace whole selection?
+ if( aSels.empty() || (nTmpMin <= nCurMin && nTmpMax >= nCurMax ) )
+ {
+ ImplClear();
+ if ( bSelect )
+ {
+ aSels.push_back( rIndexRange );
+ nSelCount = rIndexRange.Len();
+ }
+ return;
+ }
+ // expand on left side?
+ if( nTmpMax < nCurMin )
+ {
+ if( bSelect )
+ {
+ // extend first range?
+ if( nCurMin > (nTmpMax+1) )
+ {
+ aSels.insert( aSels.begin(), rIndexRange );
+ nSelCount += rIndexRange.Len();
+ }
+ else
+ {
+ auto & rRange = aSels.front();
+ nOld = rRange.Min();
+ rRange.Min() = nTmpMin;
+ nSelCount += ( nOld - nTmpMin );
+ }
+ bCurValid = false;
+ }
+ return;
+ }
+ // expand on right side?
+ else if( nTmpMin > nCurMax )
+ {
+ if( bSelect )
+ {
+ // extend last range?
+ if( nTmpMin > (nCurMax+1) )
+ {
+ aSels.push_back( rIndexRange );
+ nSelCount += rIndexRange.Len();
+ }
+ else
+ {
+ auto & rRange = aSels.back();
+ nOld = rRange.Max();
+ rRange.Max() = nTmpMax;
+ nSelCount += ( nTmpMax - nOld );
+ }
+ bCurValid = false;
+ }
+ return;
+ }
+
+ // TODO here is potential for optimization
+ while( nTmpMin <= nTmpMax )
+ {
+ Select( nTmpMin, bSelect );
+ nTmpMin++;
+ }
+}
+
+bool MultiSelection::IsSelected( sal_Int32 nIndex ) const
+{
+ // find the virtual target position
+ std::size_t nSubSelPos = ImplFindSubSelection( nIndex );
+
+ return nSubSelPos < aSels.size() && aSels[ nSubSelPos ].Contains(nIndex);
+}
+
+void MultiSelection::Insert( sal_Int32 nIndex, sal_Int32 nCount )
+{
+ // find the virtual target position
+ std::size_t nSubSelPos = ImplFindSubSelection( nIndex );
+
+ // did we need to shift the sub selections?
+ if ( nSubSelPos < aSels.size() )
+ { // did we insert an unselected into an existing sub selection?
+ if ( aSels[ nSubSelPos ].Min() != nIndex
+ && aSels[ nSubSelPos ].Contains(nIndex)
+ ) { // split the sub selection
+ if ( nSubSelPos < aSels.size() ) {
+ aSels.insert( aSels.begin() + nSubSelPos, Range( aSels[ nSubSelPos ].Min(), nIndex-1 ) );
+ } else {
+ aSels.push_back( Range( aSels[ nSubSelPos ].Min(), nIndex-1 ) );
+ }
+ ++nSubSelPos;
+ aSels[ nSubSelPos ].Min() = nIndex;
+ }
+
+ // shift the sub selections behind the inserting position
+ for ( std::size_t nPos = nSubSelPos; nPos < aSels.size(); ++nPos )
+ {
+ aSels[ nPos ].Min() += nCount;
+ aSels[ nPos ].Max() += nCount;
+ }
+ }
+
+ bCurValid = false;
+ aTotRange.Max() += nCount;
+}
+
+void MultiSelection::Remove( sal_Int32 nIndex )
+{
+ // find the virtual target position
+ std::size_t nSubSelPos = ImplFindSubSelection( nIndex );
+
+ // did we remove from an existing sub selection?
+ if ( nSubSelPos < aSels.size()
+ && aSels[ nSubSelPos ].Contains(nIndex)
+ ) {
+ // does this sub selection only contain the index to be deleted
+ if ( aSels[ nSubSelPos ].Len() == 1 ) {
+ // completely remove the sub selection
+ aSels.erase( aSels.begin() + nSubSelPos );
+ } else {
+ // shorten this sub selection
+ --( aSels[ nSubSelPos++ ].Max() );
+ }
+
+ // adjust the selected counter
+ --nSelCount;
+ }
+
+ // shift the sub selections behind the removed index
+ for ( std::size_t nPos = nSubSelPos; nPos < aSels.size(); ++nPos )
+ {
+ --( aSels[ nPos ].Min() );
+ --( aSels[ nPos ].Max() );
+ }
+
+ bCurValid = false;
+ aTotRange.Max() -= 1;
+}
+
+sal_Int32 MultiSelection::FirstSelected()
+{
+ nCurSubSel = 0;
+
+ bCurValid = !aSels.empty();
+ if ( !bCurValid )
+ return SFX_ENDOFSELECTION;
+
+ nCurIndex = aSels[ 0 ].Min();
+ return nCurIndex;
+}
+
+sal_Int32 MultiSelection::LastSelected()
+{
+ bCurValid = !aSels.empty();
+
+ if ( !bCurValid )
+ return SFX_ENDOFSELECTION;
+
+ nCurSubSel = aSels.size() - 1;
+ nCurIndex = aSels[ nCurSubSel ].Max();
+ return nCurIndex;
+}
+
+sal_Int32 MultiSelection::NextSelected()
+{
+ if ( !bCurValid )
+ return SFX_ENDOFSELECTION;
+
+ // is the next index in the current sub selection too?
+ if ( nCurIndex < aSels[ nCurSubSel ].Max() )
+ return ++nCurIndex;
+
+ // are there further sub selections?
+ if ( ++nCurSubSel >= aSels.size() )
+ // we are at the end!
+ return SFX_ENDOFSELECTION;
+
+ nCurIndex = aSels[ nCurSubSel ].Min();
+ return nCurIndex;
+}
+
+void MultiSelection::SetTotalRange( const Range& rTotRange )
+{
+ aTotRange = rTotRange;
+
+ // adjust lower boundary
+ Range* pRange = aSels.empty() ? nullptr : &aSels.front();
+ while( pRange )
+ {
+ if( pRange->Max() < aTotRange.Min() )
+ {
+ aSels.erase( aSels.begin() );
+ }
+ else if( pRange->Min() < aTotRange.Min() )
+ {
+ pRange->Min() = aTotRange.Min();
+ break;
+ }
+ else
+ break;
+
+ pRange = aSels.empty() ? nullptr : &aSels.front();
+ }
+
+ // adjust upper boundary
+ sal_Int32 nCount = aSels.size();
+ while( nCount )
+ {
+ pRange = &aSels[ nCount - 1 ];
+ if( pRange->Min() > aTotRange.Max() )
+ {
+ aSels.pop_back();
+ }
+ else if( pRange->Max() > aTotRange.Max() )
+ {
+ pRange->Max() = aTotRange.Max();
+ break;
+ }
+ else
+ break;
+
+ nCount = aSels.size();
+ }
+
+ // re-calculate selection count
+ nSelCount = 0;
+ for (Range const & rSel : aSels)
+ nSelCount += rSel.Len();
+
+ bCurValid = false;
+ nCurIndex = 0;
+}
+
+// StringRangeEnumerator
+
+StringRangeEnumerator::StringRangeEnumerator( std::u16string_view i_rInput,
+ sal_Int32 i_nMinNumber,
+ sal_Int32 i_nMaxNumber,
+ sal_Int32 i_nLogicalOffset
+ )
+ : mnCount( 0 )
+ , mnMin( i_nMinNumber )
+ , mnMax( i_nMaxNumber )
+ , mnOffset( i_nLogicalOffset )
+ , mbValidInput( false )
+{
+ // Parse string only if boundaries are valid.
+ if( mnMin >= 0 && mnMax >= 0 && mnMin <= mnMax )
+ mbValidInput = setRange( i_rInput );
+}
+
+bool StringRangeEnumerator::checkValue( sal_Int32 i_nValue, const o3tl::sorted_vector< sal_Int32 >* i_pPossibleValues ) const
+{
+ if( i_nValue < 0 || i_nValue < mnMin || i_nValue > mnMax )
+ return false;
+ if( i_pPossibleValues && i_pPossibleValues->find( i_nValue ) == i_pPossibleValues->end() )
+ return false;
+ return true;
+}
+
+bool StringRangeEnumerator::insertRange( sal_Int32 i_nFirst, sal_Int32 i_nLast, bool bSequence )
+{
+ bool bSuccess = true;
+ if( bSequence )
+ {
+ // Check if the range is completely outside of possible pages range
+ if ((i_nFirst < mnMin && i_nLast < mnMin) ||
+ (i_nFirst > mnMax && i_nLast > mnMax))
+ return false;
+ if( i_nFirst < mnMin )
+ i_nFirst = mnMin;
+ if( i_nFirst > mnMax )
+ i_nFirst = mnMax;
+ if( i_nLast < mnMin )
+ i_nLast = mnMin;
+ if( i_nLast > mnMax )
+ i_nLast = mnMax;
+ if( checkValue( i_nFirst ) && checkValue( i_nLast ) )
+ {
+ maSequence.push_back( Range( i_nFirst, i_nLast ) );
+ sal_Int32 nNumber = i_nLast - i_nFirst;
+ nNumber = nNumber < 0 ? -nNumber : nNumber;
+ mnCount += nNumber + 1;
+ }
+ else
+ bSuccess = false;
+ }
+ else
+ {
+ if( checkValue( i_nFirst ) )
+ {
+ maSequence.push_back( Range( i_nFirst, i_nFirst ) );
+ mnCount++;
+ }
+ else if( checkValue( i_nLast ) )
+ {
+ maSequence.push_back( Range( i_nLast, i_nLast ) );
+ mnCount++;
+ }
+ else
+ bSuccess = false;
+ }
+
+ return bSuccess;
+}
+
+void StringRangeEnumerator::insertJoinedRanges(
+ const std::vector< sal_Int32 >& rNumbers )
+{
+ size_t nCount = rNumbers.size();
+ if( nCount == 0 )
+ return;
+
+ if( nCount == 1 )
+ {
+ insertRange( rNumbers[0], -1, false );
+ return;
+ }
+
+ for( size_t i = 0; i < nCount - 1; i++ )
+ {
+ sal_Int32 nFirst = rNumbers[i];
+ sal_Int32 nLast = rNumbers[i + 1];
+ if( i > 0 )
+ {
+ if ( nFirst > nLast ) nFirst--;
+ else if( nFirst < nLast ) nFirst++;
+ }
+
+ insertRange( nFirst, nLast, nFirst != nLast );
+ }
+}
+
+bool StringRangeEnumerator::setRange( std::u16string_view aNewRange )
+{
+ mnCount = 0;
+ maSequence.clear();
+
+ auto pInput = aNewRange.begin();
+ auto pInputEnd = aNewRange.end();
+ OUStringBuffer aNumberBuf( 16 );
+ std::vector< sal_Int32 > aNumbers;
+ bool bSequence = false;
+ while( pInput != pInputEnd )
+ {
+ while( pInput != pInputEnd && *pInput >= '0' && *pInput <= '9' )
+ aNumberBuf.append( *pInput++ );
+ if( !aNumberBuf.isEmpty() )
+ {
+ sal_Int32 nNumber = o3tl::toInt32(aNumberBuf) + mnOffset;
+ aNumberBuf.setLength(0);
+ aNumbers.push_back( nNumber );
+ bSequence = false;
+ }
+ if (pInput == pInputEnd)
+ break;
+ if( *pInput == '-' )
+ {
+ bSequence = true;
+ if( aNumbers.empty() )
+ {
+ // push out-of-range small value, to exclude ranges totally outside of possible range
+ aNumbers.push_back( mnMin-1 );
+ }
+ }
+ else if( *pInput == ',' || *pInput == ';' )
+ {
+ if( bSequence && !aNumbers.empty() )
+ {
+ // push out-of-range large value, to exclude ranges totally outside of possible range
+ aNumbers.push_back( mnMax+1 );
+ }
+ insertJoinedRanges( aNumbers );
+
+ aNumbers.clear();
+ bSequence = false;
+ }
+ else if( *pInput != ' ' )
+ return false; // parse error
+
+ pInput++;
+ }
+ // insert last entries
+ if( bSequence && !aNumbers.empty() )
+ {
+ // push out-of-range large value, to exclude ranges totally outside of possible range
+ aNumbers.push_back( mnMax+1 );
+ }
+ insertJoinedRanges( aNumbers );
+
+ return true;
+}
+
+bool StringRangeEnumerator::hasValue( sal_Int32 i_nValue, const o3tl::sorted_vector< sal_Int32 >* i_pPossibleValues ) const
+{
+ if( i_pPossibleValues && i_pPossibleValues->find( i_nValue ) == i_pPossibleValues->end() )
+ return false;
+ size_t n = maSequence.size();
+ for( size_t i= 0; i < n; ++i )
+ {
+ const StringRangeEnumerator::Range rRange( maSequence[i] );
+ if( rRange.nFirst < rRange.nLast )
+ {
+ if( i_nValue >= rRange.nFirst && i_nValue <= rRange.nLast )
+ return true;
+ }
+ else
+ {
+ if( i_nValue >= rRange.nLast && i_nValue <= rRange.nFirst )
+ return true;
+ }
+ }
+ return false;
+}
+
+StringRangeEnumerator::Iterator& StringRangeEnumerator::Iterator::operator++()
+{
+ if( nRangeIndex >= 0 && nCurrent >= 0 && pEnumerator )
+ {
+ const StringRangeEnumerator::Range& rRange( pEnumerator->maSequence[nRangeIndex] );
+ bool bRangeChange = false;
+ if( rRange.nLast < rRange.nFirst )
+ {
+ // backward range
+ if( nCurrent > rRange.nLast )
+ nCurrent--;
+ else
+ bRangeChange = true;
+ }
+ else
+ {
+ // forward range
+ if( nCurrent < rRange.nLast )
+ nCurrent++;
+ else
+ bRangeChange = true;
+ }
+ if( bRangeChange )
+ {
+ nRangeIndex++;
+ if( size_t(nRangeIndex) == pEnumerator->maSequence.size() )
+ {
+ // reached the end
+ nRangeIndex = nCurrent = -1;
+ }
+ else
+ nCurrent = pEnumerator->maSequence[nRangeIndex].nFirst;
+ }
+ if( nRangeIndex != -1 && nCurrent != -1 )
+ {
+ if( ! pEnumerator->checkValue( nCurrent, pPossibleValues ) )
+ return ++(*this);
+ }
+ }
+ return *this;
+}
+
+
+bool StringRangeEnumerator::Iterator::operator==( const Iterator& i_rCompare ) const
+{
+ return i_rCompare.pEnumerator == pEnumerator && i_rCompare.nRangeIndex == nRangeIndex && i_rCompare.nCurrent == nCurrent;
+}
+
+StringRangeEnumerator::Iterator StringRangeEnumerator::begin( const o3tl::sorted_vector< sal_Int32 >* i_pPossibleValues ) const
+{
+ StringRangeEnumerator::Iterator it( this,
+ i_pPossibleValues,
+ maSequence.empty() ? -1 : 0,
+ maSequence.empty() ? -1 : maSequence[0].nFirst );
+ if( ! checkValue(*it, i_pPossibleValues ) )
+ ++it;
+ return it;
+}
+
+StringRangeEnumerator::Iterator StringRangeEnumerator::end( const o3tl::sorted_vector< sal_Int32 >* i_pPossibleValues ) const
+{
+ return StringRangeEnumerator::Iterator( this, i_pPossibleValues, -1, -1 );
+}
+
+bool StringRangeEnumerator::getRangesFromString( std::u16string_view i_rPageRange,
+ std::vector< sal_Int32 >& o_rPageVector,
+ sal_Int32 i_nMinNumber,
+ sal_Int32 i_nMaxNumber,
+ sal_Int32 i_nLogicalOffset,
+ o3tl::sorted_vector< sal_Int32 > const * i_pPossibleValues
+ )
+{
+ o_rPageVector.clear();
+
+ StringRangeEnumerator aEnum( i_rPageRange, i_nMinNumber, i_nMaxNumber, i_nLogicalOffset ) ;
+
+ //Even if the input range wasn't completely valid, return what ranges could
+ //be extracted from the input.
+ o_rPageVector.reserve( static_cast< size_t >( aEnum.size() ) );
+ for( StringRangeEnumerator::Iterator it = aEnum.begin( i_pPossibleValues );
+ it != aEnum.end( i_pPossibleValues ); ++it )
+ {
+ o_rPageVector.push_back( *it );
+ }
+
+ return aEnum.mbValidInput;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/misc/cpuid.cxx b/tools/source/misc/cpuid.cxx
new file mode 100644
index 0000000000..855b87e6da
--- /dev/null
+++ b/tools/source/misc/cpuid.cxx
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <tools/cpuid.hxx>
+#include <cstdint>
+
+namespace cpuid
+{
+namespace
+{
+#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64))
+#include <intrin.h>
+void getCpuId(uint32_t array[4], uint32_t nInfoType)
+{
+ __cpuid(reinterpret_cast<int*>(array), nInfoType);
+}
+#elif (defined(__i386__) || defined(__x86_64__))
+#include <cpuid.h>
+void getCpuId(uint32_t array[4], uint32_t nInfoType)
+{
+ __cpuid_count(nInfoType, 0, *(array + 0), *(array + 1), *(array + 2), *(array + 3));
+}
+#else
+void getCpuId(uint32_t array[4], uint32_t /*nInfoType*/)
+{
+ array[0] = array[1] = array[2] = array[3] = 0;
+}
+#endif
+
+// For AVX we need to check if OS has support for ymm registers
+bool checkAVXSupportInOS()
+{
+ uint32_t xcr0 = 0;
+#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64))
+ xcr0 = uint32_t(_xgetbv(0));
+#elif (defined(__i386__) || defined(__x86_64__))
+ __asm__("xgetbv" : "=a"(xcr0) : "c"(0) : "%edx");
+#endif
+ return ((xcr0 & 6) == 6); /* checking if xmm and ymm state are enabled in XCR0 */
+}
+
+} // end anonymous namespace
+
+#define HYPER_bit (1 << 28)
+#define SSE2_bit (1 << 26)
+#define SSSE3_bit (1 << 9)
+#define SSE41_bit (1 << 19)
+#define SSE42_bit (1 << 20)
+#define XSAVE_bit (1 << 27)
+#define AVX_bit (1 << 28)
+#define AVX2_bit (1 << 5)
+#define AVX512F_bit (1 << 16)
+
+InstructionSetFlags getCpuInstructionSetFlags()
+{
+ InstructionSetFlags eInstructions = InstructionSetFlags::NONE;
+
+ uint32_t info[] = { 0, 0, 0, 0 };
+ getCpuId(info, 0);
+ int nLevel = info[0];
+
+ if (nLevel >= 1)
+ {
+ uint32_t aCpuInfoArray[] = { 0, 0, 0, 0 };
+ getCpuId(aCpuInfoArray, 1);
+
+ if ((aCpuInfoArray[3] & HYPER_bit) != 0)
+ eInstructions |= InstructionSetFlags::HYPER;
+
+ if ((aCpuInfoArray[3] & SSE2_bit) != 0)
+ eInstructions |= InstructionSetFlags::SSE2;
+
+ if ((aCpuInfoArray[2] & SSSE3_bit) != 0)
+ eInstructions |= InstructionSetFlags::SSSE3;
+
+ if ((aCpuInfoArray[2] & SSE41_bit) != 0)
+ eInstructions |= InstructionSetFlags::SSE41;
+
+ if ((aCpuInfoArray[2] & SSE42_bit) != 0)
+ eInstructions |= InstructionSetFlags::SSE42;
+
+ if (((aCpuInfoArray[2] & AVX_bit) != 0) && ((aCpuInfoArray[2] & XSAVE_bit) != 0))
+ {
+ if (checkAVXSupportInOS())
+ {
+ eInstructions |= InstructionSetFlags::AVX;
+
+ if (nLevel >= 7)
+ {
+ uint32_t aExtendedInfo[] = { 0, 0, 0, 0 };
+ getCpuId(aExtendedInfo, 7);
+
+ if ((aExtendedInfo[1] & AVX2_bit) != 0)
+ eInstructions |= InstructionSetFlags::AVX2;
+ if ((aExtendedInfo[1] & AVX512F_bit) != 0)
+ eInstructions |= InstructionSetFlags::AVX512F;
+ }
+ }
+ }
+ }
+
+ return eInstructions;
+}
+
+bool isCpuInstructionSetSupported(InstructionSetFlags eInstructions)
+{
+ static InstructionSetFlags eCPUFlags = getCpuInstructionSetFlags();
+ return (eCPUFlags & eInstructions) == eInstructions;
+}
+
+OUString instructionSetSupportedString()
+{
+ OUString aString;
+ if (isCpuInstructionSetSupported(InstructionSetFlags::SSE2))
+ aString += "SSE2 ";
+ if (isCpuInstructionSetSupported(InstructionSetFlags::SSSE3))
+ aString += "SSSE3 ";
+ if (isCpuInstructionSetSupported(InstructionSetFlags::SSE41))
+ aString += "SSE4.1 ";
+ if (isCpuInstructionSetSupported(InstructionSetFlags::SSE42))
+ aString += "SSE4.2 ";
+ if (isCpuInstructionSetSupported(InstructionSetFlags::AVX))
+ aString += "AVX ";
+ if (isCpuInstructionSetSupported(InstructionSetFlags::AVX2))
+ aString += "AVX2 ";
+ if (isCpuInstructionSetSupported(InstructionSetFlags::AVX512F))
+ aString += "AVX512F ";
+ return aString;
+}
+
+} // end cpuid
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/misc/extendapplicationenvironment.cxx b/tools/source/misc/extendapplicationenvironment.cxx
new file mode 100644
index 0000000000..07f9779ccc
--- /dev/null
+++ b/tools/source/misc/extendapplicationenvironment.cxx
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_folders.h>
+
+#include <sal/config.h>
+
+#include <stdlib.h>
+
+#if defined UNX
+#include <sys/resource.h>
+#endif
+
+#include <osl/process.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <tools/extendapplicationenvironment.hxx>
+
+namespace tools
+{
+void extendApplicationEnvironment()
+{
+#if defined UNX && !defined EMSCRIPTEN
+ // Try to set RLIMIT_NOFILE as large as possible (failure is harmless):
+ rlimit lim;
+ if (getrlimit(RLIMIT_NOFILE, &lim) == 0)
+ {
+ lim.rlim_cur = lim.rlim_max;
+ setrlimit(RLIMIT_NOFILE, &lim);
+ }
+#endif
+
+ // Make sure URE_BOOTSTRAP environment variable is set (failure is fatal):
+ OUStringBuffer env(512);
+ OUString envVar("URE_BOOTSTRAP");
+ OUString uri;
+ if (rtl::Bootstrap::get(envVar, uri))
+ {
+ if (!uri.matchIgnoreAsciiCase("vnd.sun.star.pathname:"))
+ {
+ uri = rtl::Bootstrap::encode(uri);
+ }
+ env.append(uri);
+ }
+ else
+ {
+ if (osl_getExecutableFile(&uri.pData) != osl_Process_E_None)
+ {
+ abort();
+ }
+ sal_Int32 lastDirSeparatorPos = uri.lastIndexOf('/');
+ if (lastDirSeparatorPos >= 0)
+ {
+ uri = uri.copy(0, lastDirSeparatorPos + 1);
+ }
+ env.append(rtl::Bootstrap::encode(uri));
+#ifdef MACOSX
+ env.append("../" LIBO_SHARE_FOLDER "/");
+#endif
+ env.append(SAL_CONFIGFILE("fundamental"));
+ }
+ OUString envValue(env.makeStringAndClear());
+ if (osl_setEnvironment(envVar.pData, envValue.pData) != osl_Process_E_None)
+ {
+ abort();
+ }
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/misc/fix16.cxx b/tools/source/misc/fix16.cxx
new file mode 100644
index 0000000000..978f77291d
--- /dev/null
+++ b/tools/source/misc/fix16.cxx
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * libfixmath is Copyright (c) 2011-2021 Flatmush <Flatmush@gmail.com>,
+ * Petteri Aimonen <Petteri.Aimonen@gmail.com>, & libfixmath AUTHORS
+ *
+ * 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.
+ */
+
+#include <tools/fix16.hxx>
+
+const fix16_t fix16_minimum = 0x80000000; /*!< the minimum value of fix16_t */
+const fix16_t fix16_overflow = 0x80000000; /*!< the value used to indicate overflows */
+
+static inline uint32_t fix_abs(fix16_t in)
+{
+ if (in == fix16_minimum)
+ {
+ // minimum negative number has same representation as
+ // its absolute value in unsigned
+ return 0x80000000;
+ }
+ else
+ {
+ return (in >= 0) ? in : -in;
+ }
+}
+
+/* 64-bit implementation for fix16_mul. Fastest version for e.g. ARM Cortex M3.
+ * Performs a 32*32 -> 64bit multiplication. The middle 32 bits are the result,
+ * bottom 16 bits are used for rounding, and upper 16 bits are used for overflow
+ * detection.
+ */
+
+fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1)
+{
+ int64_t product = static_cast<int64_t>(inArg0) * inArg1;
+
+ // The upper 17 bits should all be the same (the sign).
+ uint32_t upper = (product >> 47);
+
+ if (product < 0)
+ {
+ if (~upper)
+ return fix16_overflow;
+
+ // This adjustment is required in order to round -1/2 correctly
+ product--;
+ }
+ else
+ {
+ if (upper)
+ return fix16_overflow;
+ }
+
+ fix16_t result = product >> 16;
+ result += (product & 0x8000) >> 15;
+
+ return result;
+}
+
+/* 32-bit implementation of fix16_div. Fastest version for e.g. ARM Cortex M3.
+ * Performs 32-bit divisions repeatedly to reduce the remainder. For this to
+ * be efficient, the processor has to have 32-bit hardware division.
+ */
+#ifdef __GNUC__
+// Count leading zeros, using processor-specific instruction if available.
+#define clz(x) (__builtin_clzl(x) - (8 * sizeof(long) - 32))
+#else
+static uint8_t clz(uint32_t x)
+{
+ uint8_t result = 0;
+ if (x == 0)
+ return 32;
+ while (!(x & 0xF0000000))
+ {
+ result += 4;
+ x <<= 4;
+ }
+ while (!(x & 0x80000000))
+ {
+ result += 1;
+ x <<= 1;
+ }
+ return result;
+}
+#endif
+
+fix16_t fix16_div(fix16_t a, fix16_t b)
+{
+ // This uses a hardware 32/32 bit division multiple times, until we have
+ // computed all the bits in (a<<17)/b. Usually this takes 1-3 iterations.
+
+ if (b == 0)
+ return fix16_minimum;
+
+ uint32_t remainder = fix_abs(a);
+ uint32_t divider = fix_abs(b);
+ uint64_t quotient = 0;
+ int bit_pos = 17;
+
+ // Kick-start the division a bit.
+ // This improves speed in the worst-case scenarios where N and D are large
+ // It gets a lower estimate for the result by N/(D >> 17 + 1).
+ if (divider & 0xFFF00000)
+ {
+ uint32_t shifted_div = (divider >> 17) + 1;
+ quotient = remainder / shifted_div;
+ uint64_t tmp = (quotient * static_cast<uint64_t>(divider)) >> 17;
+ remainder -= static_cast<uint32_t>(tmp);
+ }
+
+ // If the divider is divisible by 2^n, take advantage of it.
+ while (!(divider & 0xF) && bit_pos >= 4)
+ {
+ divider >>= 4;
+ bit_pos -= 4;
+ }
+
+ while (remainder && bit_pos >= 0)
+ {
+ // Shift remainder as much as we can without overflowing
+ int shift = clz(remainder);
+ if (shift > bit_pos)
+ shift = bit_pos;
+ remainder <<= shift;
+ bit_pos -= shift;
+
+ uint32_t div = remainder / divider;
+ remainder = remainder % divider;
+ quotient += static_cast<uint64_t>(div) << bit_pos;
+
+ if (div & ~(0xFFFFFFFF >> bit_pos))
+ return fix16_overflow;
+
+ remainder <<= 1;
+ bit_pos--;
+ }
+
+ // Quotient is always positive so rounding is easy
+ quotient++;
+
+ fix16_t result = quotient >> 1;
+
+ // Figure out the sign of the result
+ if ((a ^ b) & 0x80000000)
+ {
+ if (result == fix16_minimum)
+ return fix16_overflow;
+
+ result = -result;
+ }
+
+ return result;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/source/misc/json_writer.cxx b/tools/source/misc/json_writer.cxx
new file mode 100644
index 0000000000..e3e27bf756
--- /dev/null
+++ b/tools/source/misc/json_writer.cxx
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <tools/json_writer.hxx>
+#include <stdio.h>
+#include <cstring>
+#include <rtl/math.hxx>
+
+namespace tools
+{
+/** These buffers are short-lived, so rather waste some space and avoid the cost of
+ * repeated calls into the allocator */
+constexpr int DEFAULT_BUFFER_SIZE = 2048;
+
+JsonWriter::JsonWriter()
+ : mpBuffer(static_cast<char*>(malloc(DEFAULT_BUFFER_SIZE)))
+ , mPos(mpBuffer)
+ , mSpaceAllocated(DEFAULT_BUFFER_SIZE)
+ , mStartNodeCount(0)
+ , mbFirstFieldInNode(true)
+ , mbClosed(false)
+{
+ *mPos = '{';
+ ++mPos;
+ *mPos = ' ';
+ ++mPos;
+
+ addValidationMark();
+}
+
+JsonWriter::~JsonWriter()
+{
+ assert(mbClosed && "forgot to extract data?");
+ free(mpBuffer);
+}
+
+ScopedJsonWriterNode JsonWriter::startNode(std::string_view pNodeName)
+{
+ putLiteral(pNodeName, "{ ");
+
+ mStartNodeCount++;
+ mbFirstFieldInNode = true;
+
+ return ScopedJsonWriterNode(*this);
+}
+
+void JsonWriter::endNode()
+{
+ assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
+ --mStartNodeCount;
+ ensureSpace(1);
+ *mPos = '}';
+ ++mPos;
+ mbFirstFieldInNode = false;
+
+ validate();
+}
+
+ScopedJsonWriterArray JsonWriter::startArray(std::string_view pNodeName)
+{
+ putLiteral(pNodeName, "[ ");
+
+ mStartNodeCount++;
+ mbFirstFieldInNode = true;
+
+ return ScopedJsonWriterArray(*this);
+}
+
+void JsonWriter::endArray()
+{
+ assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
+ --mStartNodeCount;
+ ensureSpace(1);
+ *mPos = ']';
+ ++mPos;
+ mbFirstFieldInNode = false;
+
+ validate();
+}
+
+ScopedJsonWriterStruct JsonWriter::startStruct()
+{
+ ensureSpace(6);
+
+ addCommaBeforeField();
+
+ *mPos = '{';
+ ++mPos;
+ *mPos = ' ';
+ ++mPos;
+ mStartNodeCount++;
+ mbFirstFieldInNode = true;
+
+ validate();
+
+ return ScopedJsonWriterStruct(*this);
+}
+
+void JsonWriter::endStruct()
+{
+ assert(mStartNodeCount && "mismatched StartNode/EndNode somewhere");
+ --mStartNodeCount;
+ ensureSpace(1);
+ *mPos = '}';
+ ++mPos;
+ mbFirstFieldInNode = false;
+
+ validate();
+}
+
+static char getEscapementChar(char ch)
+{
+ switch (ch)
+ {
+ case '\b':
+ return 'b';
+ case '\t':
+ return 't';
+ case '\n':
+ return 'n';
+ case '\f':
+ return 'f';
+ case '\r':
+ return 'r';
+ default:
+ return ch;
+ }
+}
+
+static bool writeEscapedSequence(sal_uInt32 ch, char*& pos)
+{
+ // control characters
+ if (ch <= 0x1f)
+ {
+ int written = snprintf(pos, 7, "\\u%.4x", static_cast<unsigned int>(ch));
+ if (written > 0)
+ pos += written;
+ return true;
+ }
+
+ switch (ch)
+ {
+ case '"':
+ case '/':
+ case '\\':
+ *pos++ = '\\';
+ *pos++ = getEscapementChar(ch);
+ return true;
+ // Special processing of U+2028 and U+2029, which are valid JSON, but invalid JavaScript
+ // Write them in escaped '\u2028' or '\u2029' form
+ case 0x2028:
+ case 0x2029:
+ *pos++ = '\\';
+ *pos++ = 'u';
+ *pos++ = '2';
+ *pos++ = '0';
+ *pos++ = '2';
+ *pos++ = ch == 0x2028 ? '8' : '9';
+ return true;
+ default:
+ return false;
+ }
+}
+
+void JsonWriter::writeEscapedOUString(const OUString& rPropVal)
+{
+ *mPos = '"';
+ ++mPos;
+
+ // Convert from UTF-16 to UTF-8 and perform escaping
+ sal_Int32 i = 0;
+ while (i < rPropVal.getLength())
+ {
+ sal_uInt32 ch = rPropVal.iterateCodePoints(&i);
+ if (writeEscapedSequence(ch, mPos))
+ continue;
+ if (ch <= 0x7F)
+ {
+ *mPos = static_cast<char>(ch);
+ ++mPos;
+ }
+ else if (ch <= 0x7FF)
+ {
+ *mPos = 0xC0 | (ch >> 6); /* 110xxxxx */
+ ++mPos;
+ *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
+ ++mPos;
+ }
+ else if (ch <= 0xFFFF)
+ {
+ *mPos = 0xE0 | (ch >> 12); /* 1110xxxx */
+ ++mPos;
+ *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */
+ ++mPos;
+ *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
+ ++mPos;
+ }
+ else
+ {
+ *mPos = 0xF0 | (ch >> 18); /* 11110xxx */
+ ++mPos;
+ *mPos = 0x80 | ((ch >> 12) & 0x3F); /* 10xxxxxx */
+ ++mPos;
+ *mPos = 0x80 | ((ch >> 6) & 0x3F); /* 10xxxxxx */
+ ++mPos;
+ *mPos = 0x80 | (ch & 0x3F); /* 10xxxxxx */
+ ++mPos;
+ }
+ }
+
+ *mPos = '"';
+ ++mPos;
+
+ validate();
+}
+
+void JsonWriter::put(std::u16string_view pPropName, const OUString& rPropVal)
+{
+ auto nPropNameLength = pPropName.length();
+ // But values can be any UTF-8,
+ // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence)
+ auto nWorstCasePropValLength = rPropVal.getLength() * 6;
+ ensureSpace(nPropNameLength + nWorstCasePropValLength + 8);
+
+ addCommaBeforeField();
+
+ writeEscapedOUString(OUString(pPropName));
+
+ memcpy(mPos, ": ", 2);
+ mPos += 2;
+
+ writeEscapedOUString(rPropVal);
+
+ validate();
+}
+
+void JsonWriter::put(std::string_view pPropName, const OUString& rPropVal)
+{
+ // Values can be any UTF-8,
+ // if the string only contains of 0x2028, it will be expanded 6 times (see writeEscapedSequence)
+ auto nWorstCasePropValLength = rPropVal.getLength() * 6 + 2;
+ ensureSpaceAndWriteNameColon(pPropName, nWorstCasePropValLength);
+
+ writeEscapedOUString(rPropVal);
+}
+
+void JsonWriter::put(std::string_view pPropName, std::string_view rPropVal)
+{
+ // escaping can double the length, plus quotes
+ auto nWorstCasePropValLength = rPropVal.size() * 2 + 2;
+ ensureSpaceAndWriteNameColon(pPropName, nWorstCasePropValLength);
+
+ *mPos = '"';
+ ++mPos;
+
+ // copy and perform escaping
+ bool bReachedEnd = false;
+ for (size_t i = 0; i < rPropVal.size() && !bReachedEnd; ++i)
+ {
+ char ch = rPropVal[i];
+ switch (ch)
+ {
+ case '\b':
+ case '\t':
+ case '\n':
+ case '\f':
+ case '\r':
+ case '"':
+ case '/':
+ case '\\':
+ writeEscapedSequence(ch, mPos);
+ break;
+ case 0:
+ bReachedEnd = true;
+ break;
+ case '\xE2': // Special processing of U+2028 and U+2029
+ if (i + 2 < rPropVal.size() && rPropVal[i + 1] == '\x80'
+ && (rPropVal[i + 2] == '\xA8' || rPropVal[i + 2] == '\xA9'))
+ {
+ writeEscapedSequence(rPropVal[i + 2] == '\xA8' ? 0x2028 : 0x2029, mPos);
+ i += 2;
+ break;
+ }
+ [[fallthrough]];
+ default:
+ *mPos = ch;
+ ++mPos;
+ break;
+ }
+ }
+
+ *mPos = '"';
+ ++mPos;
+
+ validate();
+}
+
+void JsonWriter::put(std::string_view pPropName, bool nPropVal)
+{
+ putLiteral(pPropName, nPropVal ? std::string_view("true") : std::string_view("false"));
+}
+
+void JsonWriter::putSimpleValue(const OUString& rPropVal)
+{
+ auto nWorstCasePropValLength = rPropVal.getLength() * 6;
+ ensureSpace(nWorstCasePropValLength + 4);
+
+ addCommaBeforeField();
+
+ writeEscapedOUString(rPropVal);
+}
+
+void JsonWriter::putRaw(std::string_view rRawBuf)
+{
+ ensureSpace(rRawBuf.size() + 2);
+
+ addCommaBeforeField();
+
+ memcpy(mPos, rRawBuf.data(), rRawBuf.size());
+ mPos += rRawBuf.size();
+
+ validate();
+}
+
+void JsonWriter::addCommaBeforeField()
+{
+ if (mbFirstFieldInNode)
+ mbFirstFieldInNode = false;
+ else
+ {
+ *mPos = ',';
+ ++mPos;
+ *mPos = ' ';
+ ++mPos;
+ }
+}
+
+void JsonWriter::ensureSpace(int noMoreBytesRequired)
+{
+ assert(!mbClosed && "already extracted data");
+ int currentUsed = mPos - mpBuffer;
+ if (currentUsed + noMoreBytesRequired >= mSpaceAllocated)
+ {
+ auto newSize = (currentUsed + noMoreBytesRequired) * 2;
+ mpBuffer = static_cast<char*>(realloc(mpBuffer, newSize));
+ mPos = mpBuffer + currentUsed;
+ mSpaceAllocated = newSize;
+
+ addValidationMark();
+ }
+}
+
+void JsonWriter::ensureSpaceAndWriteNameColon(std::string_view name, int valSize)
+{
+ // we assume property names are ascii
+ ensureSpace(name.size() + valSize + 6);
+
+ addCommaBeforeField();
+
+ *mPos = '"';
+ ++mPos;
+ memcpy(mPos, name.data(), name.size());
+ mPos += name.size();
+ memcpy(mPos, "\": ", 3);
+ mPos += 3;
+}
+
+void JsonWriter::putLiteral(std::string_view propName, std::string_view propValue)
+{
+ ensureSpaceAndWriteNameColon(propName, propValue.size());
+ memcpy(mPos, propValue.data(), propValue.size());
+ mPos += propValue.size();
+
+ validate();
+}
+
+OString JsonWriter::finishAndGetAsOString()
+{
+ assert(mStartNodeCount == 0 && "did not close all nodes");
+ assert(!mbClosed && "data already extracted");
+ ensureSpace(2);
+ // add closing brace
+ *mPos = '}';
+ ++mPos;
+ // null-terminate
+ *mPos = 0;
+ mbClosed = true;
+
+ OString ret(mpBuffer, mPos - mpBuffer);
+ return ret;
+}
+
+bool JsonWriter::isDataEquals(std::string_view s) const
+{
+ return std::string_view(mpBuffer, static_cast<size_t>(mPos - mpBuffer)) == s;
+}
+
+} // namespace tools
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/tools/source/misc/pathutils.cxx b/tools/source/misc/pathutils.cxx
new file mode 100644
index 0000000000..706740a320
--- /dev/null
+++ b/tools/source/misc/pathutils.cxx
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#if defined(_WIN32)
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <o3tl/safeint.hxx>
+#include <sal/types.h>
+#include <tools/pathutils.hxx>
+
+namespace tools {
+
+WCHAR * filename(WCHAR * path) {
+ WCHAR * f = path;
+ for (WCHAR * p = path;;) {
+ switch (*p++) {
+ case L'\0':
+ return f;
+ case L'\\':
+ f = p;
+ break;
+ }
+ }
+}
+
+WCHAR * buildPath(
+ WCHAR * path, WCHAR const * frontBegin, WCHAR const * frontEnd,
+ WCHAR const * backBegin, std::size_t backLength)
+{
+ // Remove leading ".." segments in the second path together with matching
+ // segments in the first path that are neither empty nor "." nor ".." nor
+ // end in ":" (which is not foolproof, as it can erroneously erase the start
+ // of a UNC path, but only if the input is bad data):
+ while (backLength >= 2 && backBegin[0] == L'.' && backBegin[1] == L'.' &&
+ (backLength == 2 || backBegin[2] == L'\\'))
+ {
+ if (frontEnd - frontBegin < 2 || frontEnd[-1] != L'\\' ||
+ frontEnd[-2] == L'\\' || frontEnd[-2] == L':' ||
+ (frontEnd[-2] == L'.' &&
+ (frontEnd - frontBegin < 3 || frontEnd[-3] == L'\\' ||
+ (frontEnd[-3] == L'.' &&
+ (frontEnd - frontBegin < 4 || frontEnd[-4] == L'\\')))))
+ {
+ break;
+ }
+ WCHAR const * p = frontEnd - 1;
+ while (p != frontBegin && p[-1] != L'\\') {
+ --p;
+ }
+ if (p == frontBegin) {
+ break;
+ }
+ frontEnd = p;
+ if (backLength == 2) {
+ backBegin += 2;
+ backLength -= 2;
+ } else {
+ backBegin += 3;
+ backLength -= 3;
+ }
+ }
+ if (backLength <
+ o3tl::make_unsigned(MAX_PATH - (frontEnd - frontBegin)))
+ {
+ WCHAR * p;
+ if (frontBegin == path) {
+ p = const_cast< WCHAR * >(frontEnd);
+ } else {
+ p = path;
+ while (frontBegin != frontEnd) {
+ *p++ = *frontBegin++;
+ }
+ }
+ for (; backLength > 0; --backLength) {
+ *p++ = *backBegin++;
+ }
+ *p = L'\0';
+ return p;
+ } else {
+ SetLastError(ERROR_FILENAME_EXCED_RANGE);
+ return nullptr;
+ }
+}
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/ref/globname.cxx b/tools/source/ref/globname.cxx
new file mode 100644
index 0000000000..df8ff10943
--- /dev/null
+++ b/tools/source/ref/globname.cxx
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <string.h>
+
+#include <comphelper/mimeconfighelper.hxx>
+#include <o3tl/sprintf.hxx>
+#include <rtl/character.hxx>
+
+#include <tools/stream.hxx>
+#include <tools/globname.hxx>
+
+// SvGlobalName ----------------------------------------------------------------
+SvGlobalName::SvGlobalName( const SvGUID & rId ) :
+ m_aData( rId )
+{
+}
+
+SvGlobalName::SvGlobalName( sal_uInt32 n1, sal_uInt16 n2, sal_uInt16 n3,
+ sal_uInt8 b8, sal_uInt8 b9, sal_uInt8 b10, sal_uInt8 b11,
+ sal_uInt8 b12, sal_uInt8 b13, sal_uInt8 b14, sal_uInt8 b15 ) :
+ m_aData{ n1, n2, n3, { b8, b9, b10, b11, b12, b13, b14, b15 } }
+{
+}
+
+SvGlobalName::SvGlobalName( const css::uno::Sequence < sal_Int8 >& aSeq )
+{
+ // create SvGlobalName from a platform independent representation
+ if ( aSeq.getLength() == 16 )
+ {
+ m_aData.Data1 = ( ( ( ( ( static_cast<sal_uInt8>(aSeq[0]) << 8 ) + static_cast<sal_uInt8>(aSeq[1]) ) << 8 ) + static_cast<sal_uInt8>(aSeq[2]) ) << 8 ) + static_cast<sal_uInt8>(aSeq[3]);
+ m_aData.Data2 = ( static_cast<sal_uInt8>(aSeq[4]) << 8 ) + static_cast<sal_uInt8>(aSeq[5]);
+ m_aData.Data3 = ( static_cast<sal_uInt8>(aSeq[6]) << 8 ) + static_cast<sal_uInt8>(aSeq[7]);
+ for( int nInd = 0; nInd < 8; nInd++ )
+ m_aData.Data4[nInd] = static_cast<sal_uInt8>(aSeq[nInd+8]);
+ }
+}
+
+SvStream& WriteSvGlobalName( SvStream& rOStr, const SvGlobalName & rObj )
+{
+ rOStr.WriteUInt32( rObj.m_aData.Data1 );
+ rOStr.WriteUInt16( rObj.m_aData.Data2 );
+ rOStr.WriteUInt16( rObj.m_aData.Data3 );
+ rOStr.WriteBytes( &rObj.m_aData.Data4, 8 );
+ return rOStr;
+}
+
+SvStream& operator >> ( SvStream& rStr, SvGlobalName & rObj )
+{
+ rStr.ReadUInt32( rObj.m_aData.Data1 );
+ rStr.ReadUInt16( rObj.m_aData.Data2 );
+ rStr.ReadUInt16( rObj.m_aData.Data3 );
+ rStr.ReadBytes( &rObj.m_aData.Data4, 8 );
+ return rStr;
+}
+
+
+bool SvGlobalName::operator < ( const SvGlobalName & rObj ) const
+{
+ if( m_aData.Data3 < rObj.m_aData.Data3 )
+ return true;
+ else if( m_aData.Data3 > rObj.m_aData.Data3 )
+ return false;
+
+ if( m_aData.Data2 < rObj.m_aData.Data2 )
+ return true;
+ else if( m_aData.Data2 > rObj.m_aData.Data2 )
+ return false;
+
+ return m_aData.Data1 < rObj.m_aData.Data1;
+}
+
+bool SvGlobalName::operator == ( const SvGlobalName & rObj ) const
+{
+ return memcmp(&m_aData, &rObj.m_aData, sizeof(m_aData)) == 0;
+}
+
+void SvGlobalName::MakeFromMemory( void const * pData )
+{
+ memcpy( &m_aData, pData, sizeof( m_aData ) );
+}
+
+bool SvGlobalName::MakeId( std::u16string_view rIdStr )
+{
+ const sal_Unicode *pStr = rIdStr.data();
+ if( rIdStr.size() != 36
+ || '-' != pStr[ 8 ] || '-' != pStr[ 13 ]
+ || '-' != pStr[ 18 ] || '-' != pStr[ 23 ] )
+ return false;
+
+ SvGUID aGuid = {};
+ auto asciiHexDigitToNumber = [](sal_Unicode c) -> sal_uInt8
+ {
+ if (rtl::isAsciiDigit(c))
+ return c - '0';
+ else
+ return rtl::toAsciiUpperCase(c) - 'A' + 10;
+ };
+
+ for( int i = 0; i < 8; i++ )
+ {
+ if( !rtl::isAsciiHexDigit( *pStr ) )
+ return false;
+ aGuid.Data1 = aGuid.Data1 * 16 + asciiHexDigitToNumber( *pStr++ );
+ }
+
+ pStr++;
+ for( int i = 0; i < 4; i++ )
+ {
+ if( !rtl::isAsciiHexDigit( *pStr ) )
+ return false;
+ aGuid.Data2 = aGuid.Data2 * 16 + asciiHexDigitToNumber( *pStr++ );
+ }
+
+ pStr++;
+ for( int i = 0; i < 4; i++ )
+ {
+ if( !rtl::isAsciiHexDigit( *pStr ) )
+ return false;
+ aGuid.Data3 = aGuid.Data3 * 16 + asciiHexDigitToNumber( *pStr++ );
+ }
+
+ pStr++;
+ for( int i = 0; i < 16; i++ )
+ {
+ if( !rtl::isAsciiHexDigit( *pStr ) )
+ return false;
+ aGuid.Data4[i/2] = aGuid.Data4[i/2] * 16 + asciiHexDigitToNumber( *pStr++ );
+ if( i == 3 )
+ pStr++;
+ }
+
+ m_aData = aGuid;
+ return true;
+}
+
+OUString SvGlobalName::GetHexName() const
+{
+ char buf[ 37 ];
+ int n = o3tl::sprintf(buf,
+ "%8.8" SAL_PRIXUINT32 "-%4.4X-%4.4X-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x",
+ m_aData.Data1, m_aData.Data2, m_aData.Data3,
+ m_aData.Data4[0], m_aData.Data4[1], m_aData.Data4[2], m_aData.Data4[3],
+ m_aData.Data4[4], m_aData.Data4[5], m_aData.Data4[6], m_aData.Data4[7]);
+ assert(n == 36);
+ return OUString::createFromAscii(std::string_view(buf, n));
+}
+
+css::uno::Sequence < sal_Int8 > SvGlobalName::GetByteSequence() const
+{
+ // platform independent representation of a "GlobalName"
+ // maybe transported remotely
+ return comphelper::MimeConfigurationHelper::GetSequenceClassID(
+ m_aData.Data1, m_aData.Data2, m_aData.Data3,
+ m_aData.Data4[0], m_aData.Data4[1], m_aData.Data4[2], m_aData.Data4[3],
+ m_aData.Data4[4], m_aData.Data4[5], m_aData.Data4[6], m_aData.Data4[7]);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/ref/ref.cxx b/tools/source/ref/ref.cxx
new file mode 100644
index 0000000000..a84ae45e69
--- /dev/null
+++ b/tools/source/ref/ref.cxx
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/ref.hxx>
+#include <tools/weakbase.hxx>
+
+SvRefBase::~SvRefBase() COVERITY_NOEXCEPT_FALSE {}
+
+tools::WeakBase::~WeakBase()
+{
+ if (mpWeakConnection.is())
+ mpWeakConnection->mpReference = nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/reversemap/bestreversemap.cxx b/tools/source/reversemap/bestreversemap.cxx
new file mode 100644
index 0000000000..f124140c19
--- /dev/null
+++ b/tools/source/reversemap/bestreversemap.cxx
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+#include <rtl/textcvt.h>
+
+#include <cstdlib>
+#include <iterator>
+#include <stdio.h>
+
+namespace {
+
+struct Encoder
+{
+ rtl_UnicodeToTextConverter m_aConverter;
+ bool m_bCapable;
+ const char *m_pEncoding;
+ Encoder(rtl_TextEncoding nEncoding, const char *pEncoding)
+ : m_aConverter(rtl_createUnicodeToTextConverter(nEncoding))
+ , m_bCapable(true)
+ , m_pEncoding(pEncoding)
+ {
+ }
+ ~Encoder()
+ {
+ rtl_destroyUnicodeToTextConverter(m_aConverter);
+ }
+ void checkSupports(sal_Unicode c)
+ {
+ char aTempArray[8];
+ sal_Size nTempSize;
+ sal_uInt32 nCvtInfo;
+
+ sal_Size nChars = rtl_convertUnicodeToText(m_aConverter,
+ nullptr, &c, 1, aTempArray, sizeof(aTempArray),
+ RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR,
+ &nCvtInfo, &nTempSize);
+ m_bCapable = nChars > 0;
+ }
+ void reset()
+ {
+ m_bCapable = true;
+ }
+ bool isOK() const
+ {
+ return m_bCapable;
+ }
+ const char* getName() const
+ {
+ return m_pEncoding;
+ }
+
+};
+
+}
+
+int main()
+{
+# define EXP(x) Encoder(x, #x)
+
+ Encoder aConverters[15] =
+ {
+ EXP(RTL_TEXTENCODING_MS_1361),
+ EXP(RTL_TEXTENCODING_MS_950),
+ EXP(RTL_TEXTENCODING_MS_949),
+ EXP(RTL_TEXTENCODING_MS_936),
+ EXP(RTL_TEXTENCODING_MS_932),
+ EXP(RTL_TEXTENCODING_MS_874),
+ EXP(RTL_TEXTENCODING_MS_1258),
+ EXP(RTL_TEXTENCODING_MS_1257),
+ EXP(RTL_TEXTENCODING_MS_1256),
+ EXP(RTL_TEXTENCODING_MS_1255),
+ EXP(RTL_TEXTENCODING_MS_1254),
+ EXP(RTL_TEXTENCODING_MS_1253),
+ EXP(RTL_TEXTENCODING_MS_1251),
+ EXP(RTL_TEXTENCODING_MS_1250),
+ EXP(RTL_TEXTENCODING_MS_1252)
+ };
+
+ printf("//Do not edit manually, generated from bestreversemap.cxx\n");
+ printf("#include <rtl/textenc.h>\n");
+ printf("#include <tools/tenccvt.hxx>\n");
+ printf("rtl_TextEncoding getBestMSEncodingByChar(sal_Unicode c)\n");
+ printf("{\n");
+
+ sal_Unicode c = 0;
+ while (c < 0xFFFF)
+ {
+ for (size_t i = 0; i < std::size(aConverters); ++i)
+ aConverters[i].reset();
+
+ int nMostCapable = -1;
+
+ while(c < 0xFFFF)
+ {
+ bool bSomethingCapable = false;
+ for (size_t i = 0; i < std::size(aConverters); ++i)
+ {
+ if (aConverters[i].isOK())
+ aConverters[i].checkSupports(c);
+ if (aConverters[i].isOK())
+ {
+ bSomethingCapable = true;
+ nMostCapable = i;
+ }
+ }
+ if (!bSomethingCapable)
+ break;
+ ++c;
+ }
+ sal_Unicode cEnd = c;
+ printf(" if (c < 0x%x)\n", c);
+ printf(" return %s;\n", aConverters[nMostCapable].getName());
+ while(c < 0xFFFF)
+ {
+ bool bNothingCapable = true;
+ for (size_t i = 0; i < std::size(aConverters); ++i)
+ {
+ aConverters[i].checkSupports(c);
+ if (aConverters[i].isOK())
+ {
+ bNothingCapable = false;
+ break;
+ }
+ }
+ if (!bNothingCapable)
+ break;
+ ++c;
+ }
+ if (cEnd != c)
+ {
+ if (c < 0xFFFF)
+ {
+ printf(" if (c < 0x%x)\n", c);
+ printf(" return RTL_TEXTENCODING_DONTKNOW;\n");
+ }
+ else
+ printf(" return RTL_TEXTENCODING_DONTKNOW;\n");
+ }
+ }
+
+ printf("}\n");
+ fflush(stdout);
+
+ return EXIT_SUCCESS;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/stream/GenericTypeSerializer.cxx b/tools/source/stream/GenericTypeSerializer.cxx
new file mode 100644
index 0000000000..3eefb008ea
--- /dev/null
+++ b/tools/source/stream/GenericTypeSerializer.cxx
@@ -0,0 +1,224 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/GenericTypeSerializer.hxx>
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <vector>
+
+namespace tools
+{
+constexpr sal_uInt16 COL_NAME_USER = 0x8000;
+
+constexpr sal_Int32 RECT_EMPTY_VALUE_RIGHT_BOTTOM = -32767;
+
+void GenericTypeSerializer::readColor(Color& rColor)
+{
+ sal_uInt16 nColorNameID(0);
+
+ mrStream.ReadUInt16(nColorNameID);
+
+ if (nColorNameID & COL_NAME_USER)
+ {
+ sal_uInt16 nRed(0);
+ sal_uInt16 nGreen(0);
+ sal_uInt16 nBlue(0);
+
+ mrStream.ReadUInt16(nRed);
+ mrStream.ReadUInt16(nGreen);
+ mrStream.ReadUInt16(nBlue);
+
+ rColor = Color(nRed >> 8, nGreen >> 8, nBlue >> 8);
+ }
+ else
+ {
+ static const std::vector<Color> staticColorArray = {
+ COL_BLACK, // COL_BLACK
+ COL_BLUE, // COL_BLUE
+ COL_GREEN, // COL_GREEN
+ COL_CYAN, // COL_CYAN
+ COL_RED, // COL_RED
+ COL_MAGENTA, // COL_MAGENTA
+ COL_BROWN, // COL_BROWN
+ COL_GRAY, // COL_GRAY
+ COL_LIGHTGRAY, // COL_LIGHTGRAY
+ COL_LIGHTBLUE, // COL_LIGHTBLUE
+ COL_LIGHTGREEN, // COL_LIGHTGREEN
+ COL_LIGHTCYAN, // COL_LIGHTCYAN
+ COL_LIGHTRED, // COL_LIGHTRED
+ COL_LIGHTMAGENTA, // COL_LIGHTMAGENTA
+ COL_YELLOW, // COL_YELLOW
+ COL_WHITE, // COL_WHITE
+ COL_WHITE, // COL_MENUBAR
+ COL_BLACK, // COL_MENUBARTEXT
+ COL_WHITE, // COL_POPUPMENU
+ COL_BLACK, // COL_POPUPMENUTEXT
+ COL_BLACK, // COL_WINDOWTEXT
+ COL_WHITE, // COL_WINDOWWORKSPACE
+ COL_BLACK, // COL_HIGHLIGHT
+ COL_WHITE, // COL_HIGHLIGHTTEXT
+ COL_BLACK, // COL_3DTEXT
+ COL_LIGHTGRAY, // COL_3DFACE
+ COL_WHITE, // COL_3DLIGHT
+ COL_GRAY, // COL_3DSHADOW
+ COL_LIGHTGRAY, // COL_SCROLLBAR
+ COL_WHITE, // COL_FIELD
+ COL_BLACK // COL_FIELDTEXT
+ };
+
+ if (nColorNameID < staticColorArray.size())
+ rColor = staticColorArray[nColorNameID];
+ else
+ rColor = COL_BLACK;
+ }
+}
+
+void GenericTypeSerializer::writeColor(const Color& rColor)
+{
+ mrStream.WriteUInt16(COL_NAME_USER);
+
+ sal_uInt16 nR = rColor.GetRed();
+ sal_uInt16 nG = rColor.GetGreen();
+ sal_uInt16 nB = rColor.GetBlue();
+
+ mrStream.WriteUInt16((nR << 8) + nR);
+ mrStream.WriteUInt16((nG << 8) + nG);
+ mrStream.WriteUInt16((nB << 8) + nB);
+}
+
+void GenericTypeSerializer::readPoint(Point& rPoint)
+{
+ sal_Int32 nX(0);
+ sal_Int32 nY(0);
+
+ mrStream.ReadInt32(nX);
+ mrStream.ReadInt32(nY);
+
+ rPoint.setX(nX);
+ rPoint.setY(nY);
+}
+
+void GenericTypeSerializer::writePoint(const Point& rPoint)
+{
+ mrStream.WriteInt32(rPoint.getX());
+ mrStream.WriteInt32(rPoint.getY());
+}
+
+void GenericTypeSerializer::readSize(Size& rSize)
+{
+ sal_Int32 nWidth(0);
+ sal_Int32 nHeight(0);
+
+ mrStream.ReadInt32(nWidth);
+ mrStream.ReadInt32(nHeight);
+
+ rSize.setWidth(nWidth);
+ rSize.setHeight(nHeight);
+
+ // sanitize negative size dimensions
+ if (rSize.Width() < 0)
+ {
+ SAL_WARN("tools", "negative width");
+ rSize.setWidth(0);
+ }
+ if (rSize.Height() < 0)
+ {
+ SAL_WARN("tools", "negative height");
+ rSize.setHeight(0);
+ }
+}
+
+void GenericTypeSerializer::writeSize(const Size& rSize)
+{
+ mrStream.WriteInt32(rSize.getWidth());
+ mrStream.WriteInt32(rSize.getHeight());
+}
+
+void GenericTypeSerializer::readRectangle(Rectangle& rRectangle)
+{
+ sal_Int32 nLeft(0);
+ sal_Int32 nTop(0);
+ sal_Int32 nRight(0);
+ sal_Int32 nBottom(0);
+
+ mrStream.ReadInt32(nLeft);
+ mrStream.ReadInt32(nTop);
+ mrStream.ReadInt32(nRight);
+ mrStream.ReadInt32(nBottom);
+
+ if (nRight == RECT_EMPTY_VALUE_RIGHT_BOTTOM || nBottom == RECT_EMPTY_VALUE_RIGHT_BOTTOM)
+ {
+ rRectangle.SetEmpty();
+ }
+ else
+ {
+ rRectangle.SetLeft(nLeft);
+ rRectangle.SetTop(nTop);
+ rRectangle.SetRight(nRight);
+ rRectangle.SetBottom(nBottom);
+ }
+}
+
+void GenericTypeSerializer::writeRectangle(const Rectangle& rRectangle)
+{
+ if (rRectangle.IsEmpty())
+ {
+ mrStream.WriteInt32(0);
+ mrStream.WriteInt32(0);
+ mrStream.WriteInt32(RECT_EMPTY_VALUE_RIGHT_BOTTOM);
+ mrStream.WriteInt32(RECT_EMPTY_VALUE_RIGHT_BOTTOM);
+ }
+ else
+ {
+ mrStream.WriteInt32(rRectangle.Left());
+ mrStream.WriteInt32(rRectangle.Top());
+ mrStream.WriteInt32(rRectangle.Right());
+ mrStream.WriteInt32(rRectangle.Bottom());
+ }
+}
+
+void GenericTypeSerializer::readFraction(Fraction& rFraction)
+{
+ sal_Int32 nNumerator(0);
+ sal_Int32 nDenominator(0);
+
+ mrStream.ReadInt32(nNumerator);
+ mrStream.ReadInt32(nDenominator);
+
+ rFraction = Fraction(nNumerator, nDenominator);
+}
+
+void GenericTypeSerializer::writeFraction(Fraction const& rFraction)
+{
+ if (!rFraction.IsValid())
+ {
+ SAL_WARN("tools.fraction", "'writeFraction()' write an invalid fraction");
+ mrStream.WriteInt32(0);
+ mrStream.WriteInt32(0);
+ }
+ else
+ {
+ mrStream.WriteInt32(rFraction.GetNumerator());
+ mrStream.WriteInt32(rFraction.GetDenominator());
+ }
+}
+
+} // end namespace tools
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/stream/stream.cxx b/tools/source/stream/stream.cxx
new file mode 100644
index 0000000000..6318348de5
--- /dev/null
+++ b/tools/source/stream/stream.cxx
@@ -0,0 +1,2001 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+// TODO: Read->RefreshBuffer-> React to changes from m_nBufActualLen
+
+#include <sal/config.h>
+
+#include <cassert>
+#include <cstddef>
+#include <memory>
+
+#include <string.h>
+
+#include <o3tl/safeint.hxx>
+#include <osl/endian.h>
+#include <osl/diagnose.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/string.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/long.hxx>
+
+#include <comphelper/fileformat.h>
+#include <comphelper/fileurl.hxx>
+
+static void swapNibbles(unsigned char &c)
+{
+ unsigned char nSwapTmp=c;
+ nSwapTmp <<= 4;
+ c >>= 4;
+ c |= nSwapTmp;
+}
+
+#include <tools/debug.hxx>
+#include <tools/stream.hxx>
+#include <osl/thread.h>
+#include <algorithm>
+
+// !!! Do not inline if already the operators <<,>> are inline
+template <typename T, std::enable_if_t<std::is_integral_v<T> && sizeof(T) == 2, int> = 0>
+static void SwapNumber(T& r)
+ { r = OSL_SWAPWORD(r); }
+template <typename T, std::enable_if_t<std::is_integral_v<T> && sizeof(T) == 4, int> = 0>
+static void SwapNumber(T& r)
+ { r = OSL_SWAPDWORD(r); }
+template <typename T, std::enable_if_t<std::is_integral_v<T> && sizeof(T) == 8, int> = 0>
+static void SwapNumber(T& r)
+ {
+ union
+ {
+ T n;
+ sal_uInt32 c[2];
+ } s;
+
+ s.n = r;
+ std::swap(s.c[0], s.c[1]); // swap the 32 bit words
+ // swap the bytes in the words
+ s.c[0] = OSL_SWAPDWORD(s.c[0]);
+ s.c[1] = OSL_SWAPDWORD(s.c[1]);
+ r = s.n;
+ }
+
+#ifdef UNX
+static void SwapFloat( float& r )
+ {
+ union
+ {
+ float f;
+ sal_uInt32 c;
+ } s;
+
+ s.f = r;
+ s.c = OSL_SWAPDWORD( s.c );
+ r = s.f;
+ }
+
+static void SwapDouble( double& r )
+ {
+ if( sizeof(double) != 8 )
+ {
+ SAL_WARN( "tools.stream", "Can only swap 8-Byte-doubles" );
+ }
+ else
+ {
+ union
+ {
+ double d;
+ sal_uInt32 c[2];
+ } s;
+
+ s.d = r;
+ s.c[0] ^= s.c[1]; // swap 32-bit values in situ
+ s.c[1] ^= s.c[0];
+ s.c[0] ^= s.c[1];
+ s.c[0] = OSL_SWAPDWORD(s.c[0]); // swap dword itself in situ
+ s.c[1] = OSL_SWAPDWORD(s.c[1]);
+ r = s.d;
+ }
+ }
+#endif
+
+//SDO
+
+void SvStream::readNumberWithoutSwap_(void * pDataDest, int nDataSize)
+{
+ if (m_isIoRead && nDataSize <= m_nBufFree)
+ {
+ for (int i = 0; i < nDataSize; i++)
+ static_cast<char*>(pDataDest)[i] = m_pBufPos[i];
+ m_nBufActualPos += nDataSize;
+ m_pBufPos += nDataSize;
+ m_nBufFree -= nDataSize;
+ }
+ else
+ {
+ ReadBytes( pDataDest, nDataSize );
+ }
+}
+
+
+void SvStream::writeNumberWithoutSwap_(const void * pDataSrc, int nDataSize)
+{
+ if (m_isIoWrite && nDataSize <= m_nBufFree)
+ {
+ for (int i = 0; i < nDataSize; i++)
+ m_pBufPos[i] = static_cast<const char*>(pDataSrc)[i];
+ m_nBufFree -= nDataSize;
+ m_nBufActualPos += nDataSize;
+ if (m_nBufActualPos > m_nBufActualLen)
+ m_nBufActualLen = m_nBufActualPos;
+ m_pBufPos += nDataSize;
+ m_isDirty = true;
+ }
+ else
+ {
+ WriteBytes( pDataSrc, nDataSize );
+ }
+}
+
+
+void SvLockBytes::close()
+{
+ if (m_bOwner)
+ delete m_pStream;
+ m_pStream = nullptr;
+}
+
+
+// virtual
+ErrCode SvLockBytes::ReadAt(sal_uInt64 const nPos, void * pBuffer, std::size_t nCount,
+ std::size_t * pRead) const
+{
+ if (!m_pStream)
+ {
+ OSL_FAIL("SvLockBytes::ReadAt(): Bad stream");
+ return ERRCODE_NONE;
+ }
+
+ m_pStream->Seek(nPos);
+ std::size_t nTheRead = m_pStream->ReadBytes(pBuffer, nCount);
+ if (pRead)
+ *pRead = nTheRead;
+ return m_pStream->GetErrorCode();
+}
+
+// virtual
+ErrCode SvLockBytes::WriteAt(sal_uInt64 const nPos, const void * pBuffer, std::size_t nCount,
+ std::size_t * pWritten)
+{
+ if (!m_pStream)
+ {
+ OSL_FAIL("SvLockBytes::WriteAt(): Bad stream");
+ return ERRCODE_NONE;
+ }
+
+ m_pStream->Seek(nPos);
+ std::size_t nTheWritten = m_pStream->WriteBytes(pBuffer, nCount);
+ if (pWritten)
+ *pWritten = nTheWritten;
+ return m_pStream->GetErrorCode();
+}
+
+// virtual
+ErrCode SvLockBytes::Flush() const
+{
+ if (!m_pStream)
+ {
+ OSL_FAIL("SvLockBytes::Flush(): Bad stream");
+ return ERRCODE_NONE;
+ }
+
+ m_pStream->Flush();
+ return m_pStream->GetErrorCode();
+}
+
+// virtual
+ErrCode SvLockBytes::SetSize(sal_uInt64 const nSize)
+{
+ if (!m_pStream)
+ {
+ OSL_FAIL("SvLockBytes::SetSize(): Bad stream");
+ return ERRCODE_NONE;
+ }
+
+ m_pStream->SetStreamSize(nSize);
+ return m_pStream->GetErrorCode();
+}
+
+ErrCode SvLockBytes::Stat(SvLockBytesStat * pStat) const
+{
+ if (!m_pStream)
+ {
+ OSL_FAIL("SvLockBytes::Stat(): Bad stream");
+ return ERRCODE_NONE;
+ }
+
+ if (pStat)
+ pStat->nSize = m_pStream->TellEnd();
+ return ERRCODE_NONE;
+}
+
+
+std::size_t SvStream::GetData( void* pData, std::size_t nSize )
+{
+ if( !GetError() )
+ {
+ DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" );
+ std::size_t nRet(0);
+ m_nError = m_xLockBytes->ReadAt(m_nActPos, pData, nSize, &nRet);
+ m_nActPos += nRet;
+ return nRet;
+ }
+ else return 0;
+}
+
+std::size_t SvStream::PutData( const void* pData, std::size_t nSize )
+{
+ if( !GetError() )
+ {
+ DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" );
+ std::size_t nRet(0);
+ m_nError = m_xLockBytes->WriteAt(m_nActPos, pData, nSize, &nRet);
+ m_nActPos += nRet;
+ return nRet;
+ }
+ else return 0;
+}
+
+sal_uInt64 SvStream::SeekPos(sal_uInt64 const nPos)
+{
+ // check if a truncated STREAM_SEEK_TO_END was passed
+ assert(nPos != SAL_MAX_UINT32);
+ if( !GetError() && nPos == STREAM_SEEK_TO_END )
+ {
+ DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" );
+ SvLockBytesStat aStat;
+ m_xLockBytes->Stat( &aStat );
+ m_nActPos = aStat.nSize;
+ }
+ else
+ m_nActPos = nPos;
+ return m_nActPos;
+}
+
+void SvStream::FlushData()
+{
+ if( !GetError() )
+ {
+ DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" );
+ m_nError = m_xLockBytes->Flush();
+ }
+}
+
+void SvStream::SetSize(sal_uInt64 const nSize)
+{
+ DBG_ASSERT( m_xLockBytes.is(), "pure virtual function" );
+ m_nError = m_xLockBytes->SetSize( nSize );
+}
+
+SvStream::SvStream() :
+ m_nActPos(0)
+
+ , m_pBufPos(nullptr)
+ , m_nBufSize(0)
+ , m_nBufActualLen(0)
+ , m_nBufActualPos(0)
+ , m_nBufFree(0)
+ , m_isIoRead(false)
+ , m_isIoWrite(false)
+
+ , m_isDirty(false)
+ , m_isEof(false)
+
+ , m_nCompressMode(SvStreamCompressFlags::NONE)
+#if defined UNX
+ , m_eLineDelimiter(LINEEND_LF) // UNIX-Format
+#else
+ , m_eLineDelimiter(LINEEND_CRLF) // DOS-Format
+#endif
+ , m_eStreamCharSet(osl_getThreadTextEncoding())
+
+ , m_nCryptMask(0)
+
+ , m_nVersion(0)
+
+ , m_nBufFilePos(0)
+ , m_eStreamMode(StreamMode::NONE)
+ , m_isWritable(true)
+
+{
+ SetEndian( SvStreamEndian::LITTLE );
+
+ ClearError();
+}
+
+SvStream::SvStream( SvLockBytes* pLockBytesP ) : SvStream()
+{
+ m_xLockBytes = pLockBytesP;
+ if( pLockBytesP ) {
+ const SvStream* pStrm = pLockBytesP->GetStream();
+ if( pStrm ) {
+ SetError( pStrm->GetErrorCode() );
+ }
+ }
+ SetBufferSize( 256 );
+}
+
+SvStream::~SvStream()
+{
+ if (m_xLockBytes.is())
+ Flush();
+}
+
+void SvStream::ClearError()
+{
+ m_isEof = false;
+ m_nError = ERRCODE_NONE;
+}
+
+void SvStream::SetError( ErrCode nErrorCode )
+{
+ if (m_nError == ERRCODE_NONE)
+ m_nError = nErrorCode;
+}
+
+void SvStream::SetEndian( SvStreamEndian nNewFormat )
+{
+#ifdef OSL_BIGENDIAN
+ m_isSwap = nNewFormat == SvStreamEndian::LITTLE;
+#else
+ m_isSwap = nNewFormat == SvStreamEndian::BIG;
+#endif
+}
+
+SvStreamEndian SvStream::GetEndian() const
+{
+#ifdef OSL_BIGENDIAN
+ return m_isSwap ? SvStreamEndian::LITTLE : SvStreamEndian::BIG;
+#else
+ return m_isSwap ? SvStreamEndian::BIG : SvStreamEndian::LITTLE;
+#endif
+}
+
+void SvStream::SetBufferSize( sal_uInt16 nBufferSize )
+{
+ sal_uInt64 const nActualFilePos = Tell();
+ bool bDontSeek = (m_pRWBuf == nullptr);
+
+ if (m_isDirty && m_isWritable) // due to Windows NT: Access denied
+ FlushBuffer();
+
+ if (m_nBufSize)
+ {
+ m_pRWBuf.reset();
+ m_nBufFilePos += m_nBufActualPos;
+ }
+
+ m_pRWBuf = nullptr;
+ m_nBufActualLen = 0;
+ m_nBufActualPos = 0;
+ m_nBufSize = nBufferSize;
+ if (m_nBufSize)
+ m_pRWBuf.reset(new sal_uInt8[ m_nBufSize ]);
+ m_pBufPos = m_pRWBuf.get();
+ m_isIoRead = m_isIoWrite = false;
+ if( !bDontSeek )
+ SeekPos( nActualFilePos );
+}
+
+void SvStream::ClearBuffer()
+{
+ m_nBufActualLen = 0;
+ m_nBufActualPos = 0;
+ m_nBufFilePos = 0;
+ m_pBufPos = m_pRWBuf.get();
+ m_isDirty = false;
+ m_isIoRead = m_isIoWrite = false;
+
+ m_isEof = false;
+}
+
+void SvStream::ResetError()
+{
+ ClearError();
+}
+
+bool SvStream::ReadByteStringLine( OUString& rStr, rtl_TextEncoding eSrcCharSet,
+ sal_Int32 nMaxBytesToRead )
+{
+ OString aStr;
+ bool bRet = ReadLine( aStr, nMaxBytesToRead);
+ rStr = OStringToOUString(aStr, eSrcCharSet);
+ return bRet;
+}
+
+bool SvStream::ReadLine( OString& rStr, sal_Int32 nMaxBytesToRead )
+{
+ OStringBuffer aBuf(4096);
+ bool rv = ReadLine(aBuf, nMaxBytesToRead);
+ rStr = aBuf.makeStringAndClear();
+ return rv;
+}
+
+bool SvStream::ReadLine( OStringBuffer& aBuf, sal_Int32 nMaxBytesToRead )
+{
+ char buf[256+1];
+ bool bEnd = false;
+ sal_uInt64 nOldFilePos = Tell();
+ char c = 0;
+ std::size_t nTotalLen = 0;
+
+ aBuf.setLength(0);
+ while( !bEnd && !GetError() ) // Don't test for EOF as we
+ // are reading block-wise!
+ {
+ sal_uInt16 nLen = static_cast<sal_uInt16>(ReadBytes(buf, sizeof(buf)-1));
+ if ( !nLen )
+ {
+ if ( aBuf.isEmpty() )
+ {
+ // Exit on first block-read error
+ m_isEof = true;
+ aBuf.setLength(0);
+ return false;
+ }
+ else
+ break;
+ }
+
+ sal_uInt16 j, n;
+ for( j = n = 0; j < nLen ; ++j )
+ {
+ c = buf[j];
+ if ( c == '\n' || c == '\r' )
+ {
+ bEnd = true;
+ break;
+ }
+ if ( n < j )
+ buf[n] = c;
+ ++n;
+ }
+ nTotalLen += j;
+ if (nTotalLen > o3tl::make_unsigned(nMaxBytesToRead))
+ {
+ n -= nTotalLen - nMaxBytesToRead;
+ nTotalLen = nMaxBytesToRead;
+ bEnd = true;
+ }
+ if ( n )
+ aBuf.append(buf, n);
+ }
+
+ if ( !bEnd && !GetError() && !aBuf.isEmpty() )
+ bEnd = true;
+
+ nOldFilePos += nTotalLen;
+ if( Tell() > nOldFilePos )
+ nOldFilePos++;
+ Seek( nOldFilePos ); // Seek pointer due to BlockRead above
+
+ if ( bEnd && (c=='\r' || c=='\n') ) // Special treatment for DOS files
+ {
+ char cTemp;
+ std::size_t nLen = ReadBytes(&cTemp, sizeof(cTemp));
+ if ( nLen ) {
+ if( cTemp == c || (cTemp != '\n' && cTemp != '\r') )
+ Seek( nOldFilePos );
+ }
+ }
+
+ if ( bEnd )
+ m_isEof = false;
+ return bEnd;
+}
+
+bool SvStream::ReadUniStringLine( OUString& rStr, sal_Int32 nMaxCodepointsToRead )
+{
+ sal_Unicode buf[256+1];
+ bool bEnd = false;
+ sal_uInt64 nOldFilePos = Tell();
+ sal_Unicode c = 0;
+ std::size_t nTotalLen = 0;
+
+ DBG_ASSERT( sizeof(sal_Unicode) == sizeof(sal_uInt16), "ReadUniStringLine: swapping sizeof(sal_Unicode) not implemented" );
+
+ OUStringBuffer aBuf(4096);
+ while( !bEnd && !GetError() ) // Don't test for EOF as we
+ // are reading block-wise!
+ {
+ sal_uInt16 nLen = static_cast<sal_uInt16>(ReadBytes( buf, sizeof(buf)-sizeof(sal_Unicode)));
+ nLen /= sizeof(sal_Unicode);
+ if ( !nLen )
+ {
+ if ( aBuf.isEmpty() )
+ {
+ // exit on first BlockRead error
+ m_isEof = true;
+ rStr.clear();
+ return false;
+ }
+ else
+ break;
+ }
+
+ sal_uInt16 j, n;
+ for( j = n = 0; j < nLen ; ++j )
+ {
+ if (m_isSwap)
+ SwapNumber( buf[n] );
+ c = buf[j];
+ if ( c == '\n' || c == '\r' )
+ {
+ bEnd = true;
+ break;
+ }
+ // erAck 26.02.01: Old behavior was no special treatment of '\0'
+ // character here, but a following rStr+=c did ignore it. Is this
+ // really intended? Or should a '\0' better terminate a line?
+ // The nOldFilePos stuff wasn't correct then anyways.
+ if ( c )
+ {
+ if ( n < j )
+ buf[n] = c;
+ ++n;
+ }
+ }
+ nTotalLen += j;
+ if (nTotalLen > o3tl::make_unsigned(nMaxCodepointsToRead))
+ {
+ n -= nTotalLen - nMaxCodepointsToRead;
+ nTotalLen = nMaxCodepointsToRead;
+ bEnd = true;
+ }
+ if ( n )
+ aBuf.append( buf, n );
+ }
+
+ if ( !bEnd && !GetError() && !aBuf.isEmpty() )
+ bEnd = true;
+
+ nOldFilePos += nTotalLen * sizeof(sal_Unicode);
+ if( Tell() > nOldFilePos )
+ nOldFilePos += sizeof(sal_Unicode);
+ Seek( nOldFilePos ); // seek due to BlockRead above
+
+ if ( bEnd && (c=='\r' || c=='\n') ) // special treatment for DOS files
+ {
+ sal_Unicode cTemp;
+ ReadBytes( &cTemp, sizeof(cTemp) );
+ if (m_isSwap)
+ SwapNumber( cTemp );
+ if( cTemp == c || (cTemp != '\n' && cTemp != '\r') )
+ Seek( nOldFilePos );
+ }
+
+ if ( bEnd )
+ m_isEof = false;
+ rStr = aBuf.makeStringAndClear();
+ return bEnd;
+}
+
+bool SvStream::ReadUniOrByteStringLine( OUString& rStr, rtl_TextEncoding eSrcCharSet,
+ sal_Int32 nMaxCodepointsToRead )
+{
+ if ( eSrcCharSet == RTL_TEXTENCODING_UNICODE )
+ return ReadUniStringLine( rStr, nMaxCodepointsToRead );
+ else
+ return ReadByteStringLine( rStr, eSrcCharSet, nMaxCodepointsToRead );
+}
+
+OString read_zeroTerminated_uInt8s_ToOString(SvStream& rStream)
+{
+ OStringBuffer aOutput(256);
+
+ char buf[ 256 + 1 ];
+ bool bEnd = false;
+ sal_uInt64 nFilePos = rStream.Tell();
+
+ while( !bEnd && !rStream.GetError() )
+ {
+ std::size_t nLen = rStream.ReadBytes(buf, sizeof(buf)-1);
+ if (!nLen)
+ break;
+
+ std::size_t nReallyRead = nLen;
+ const char* pPtr = buf;
+ while (nLen && *pPtr)
+ {
+ ++pPtr;
+ --nLen;
+ }
+
+ bEnd = ( nReallyRead < sizeof(buf)-1 ) // read less than attempted to read
+ || ( ( nLen > 0 ) // OR it is inside the block we read
+ && ( 0 == *pPtr ) // AND found a string terminator
+ );
+
+ aOutput.append(buf, pPtr - buf);
+ }
+
+ nFilePos += aOutput.getLength();
+ if (rStream.Tell() > nFilePos)
+ rStream.Seek(nFilePos+1); // seek due to FileRead above
+ return aOutput.makeStringAndClear();
+}
+
+OUString read_zeroTerminated_uInt8s_ToOUString(SvStream& rStream, rtl_TextEncoding eEnc)
+{
+ return OStringToOUString(
+ read_zeroTerminated_uInt8s_ToOString(rStream), eEnc);
+}
+
+/** Attempt to write a prefixed sequence of nUnits 16bit units from an OUString,
+ returned value is number of bytes written */
+std::size_t write_uInt16s_FromOUString(SvStream& rStrm, std::u16string_view rStr,
+ std::size_t nUnits)
+{
+ DBG_ASSERT( sizeof(sal_Unicode) == sizeof(sal_uInt16), "write_uInt16s_FromOUString: swapping sizeof(sal_Unicode) not implemented" );
+ std::size_t nWritten;
+ if (!rStrm.IsEndianSwap())
+ nWritten = rStrm.WriteBytes(rStr.data(), nUnits * sizeof(sal_Unicode));
+ else
+ {
+ std::size_t nLen = nUnits;
+ sal_Unicode aBuf[384];
+ sal_Unicode* const pTmp = ( nLen > 384 ? new sal_Unicode[nLen] : aBuf);
+ memcpy( pTmp, rStr.data(), nLen * sizeof(sal_Unicode) );
+ sal_Unicode* p = pTmp;
+ const sal_Unicode* const pStop = pTmp + nLen;
+ while ( p < pStop )
+ {
+ SwapNumber( *p );
+ p++;
+ }
+ nWritten = rStrm.WriteBytes( pTmp, nLen * sizeof(sal_Unicode) );
+ if ( pTmp != aBuf )
+ delete [] pTmp;
+ }
+ return nWritten;
+}
+
+bool SvStream::WriteUnicodeOrByteText( std::u16string_view rStr, rtl_TextEncoding eDestCharSet )
+{
+ if ( eDestCharSet == RTL_TEXTENCODING_UNICODE )
+ {
+ write_uInt16s_FromOUString(*this, rStr, rStr.size());
+ return m_nError == ERRCODE_NONE;
+ }
+ else
+ {
+ OString aStr(OUStringToOString(rStr, eDestCharSet));
+ write_uInt8s_FromOString(*this, aStr, aStr.getLength());
+ return m_nError == ERRCODE_NONE;
+ }
+}
+
+bool SvStream::WriteByteStringLine( std::u16string_view rStr, rtl_TextEncoding eDestCharSet )
+{
+ return WriteLine(OUStringToOString(rStr, eDestCharSet));
+}
+
+bool SvStream::WriteLine(std::string_view rStr)
+{
+ WriteBytes(rStr.data(), rStr.size());
+ endl(*this);
+ return m_nError == ERRCODE_NONE;
+}
+
+bool SvStream::WriteUniOrByteChar( sal_Unicode ch, rtl_TextEncoding eDestCharSet )
+{
+ if ( eDestCharSet == RTL_TEXTENCODING_UNICODE )
+ WriteUnicode(ch);
+ else
+ {
+ OString aStr(&ch, 1, eDestCharSet);
+ WriteBytes(aStr.getStr(), aStr.getLength());
+ }
+ return m_nError == ERRCODE_NONE;
+}
+
+void SvStream::StartWritingUnicodeText()
+{
+ m_isSwap = false; // Switch to no endian swapping
+ // BOM, Byte Order Mark, U+FEFF, see
+ // http://www.unicode.org/faq/utf_bom.html#BOM
+ // Upon read: 0xfeff(-257) => no swap; 0xfffe(-2) => swap
+ WriteUInt16(0xfeff);
+}
+
+void SvStream::StartReadingUnicodeText( rtl_TextEncoding eReadBomCharSet )
+{
+ if (!( eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW ||
+ eReadBomCharSet == RTL_TEXTENCODING_UNICODE ||
+ eReadBomCharSet == RTL_TEXTENCODING_UTF8))
+ return; // nothing to read
+
+ const sal_uInt64 nOldPos = Tell();
+ bool bGetBack = true;
+ unsigned char nFlag(0);
+ ReadUChar( nFlag );
+ switch ( nFlag )
+ {
+ case 0xfe: // UTF-16BE?
+ if ( eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW ||
+ eReadBomCharSet == RTL_TEXTENCODING_UNICODE)
+ {
+ ReadUChar(nFlag);
+ if (nFlag == 0xff)
+ {
+ SetEndian(SvStreamEndian::BIG);
+ bGetBack = false;
+ }
+ }
+ break;
+ case 0xff: // UTF-16LE?
+ if ( eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW ||
+ eReadBomCharSet == RTL_TEXTENCODING_UNICODE)
+ {
+ ReadUChar(nFlag);
+ if (nFlag == 0xfe)
+ {
+ SetEndian(SvStreamEndian::LITTLE);
+ bGetBack = false;
+ }
+ }
+ break;
+ case 0xef: // UTF-8?
+ if ( eReadBomCharSet == RTL_TEXTENCODING_DONTKNOW ||
+ eReadBomCharSet == RTL_TEXTENCODING_UTF8)
+ {
+ ReadUChar(nFlag);
+ if (nFlag == 0xbb)
+ {
+ ReadUChar(nFlag);
+ if (nFlag == 0xbf)
+ bGetBack = false; // it is UTF-8
+ }
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ if (bGetBack)
+ Seek(nOldPos); // no BOM, pure data
+}
+
+sal_uInt64 SvStream::SeekRel(sal_Int64 const nPos)
+{
+ sal_uInt64 nActualPos = Tell();
+
+ if ( nPos >= 0 )
+ {
+ if (SAL_MAX_UINT64 - nActualPos > o3tl::make_unsigned(nPos))
+ nActualPos += nPos;
+ }
+ else
+ {
+ sal_uInt64 const nAbsPos = static_cast<sal_uInt64>(-nPos);
+ if ( nActualPos >= nAbsPos )
+ nActualPos -= nAbsPos;
+ }
+
+ assert((m_pBufPos != nullptr) == bool(m_pRWBuf));
+ if (m_pRWBuf)
+ {
+ m_pBufPos = m_pRWBuf.get() + nActualPos;
+ }
+ return Seek( nActualPos );
+}
+
+template <typename T> SvStream& SvStream::ReadNumber(T& r)
+{
+ T n = 0;
+ readNumberWithoutSwap(n);
+ if (good())
+ {
+ if (m_isSwap)
+ SwapNumber(n);
+ r = n;
+ }
+ return *this;
+}
+
+SvStream& SvStream::ReadUInt16(sal_uInt16& r) { return ReadNumber(r); }
+SvStream& SvStream::ReadUInt32(sal_uInt32& r) { return ReadNumber(r); }
+SvStream& SvStream::ReadUInt64(sal_uInt64& r) { return ReadNumber(r); }
+SvStream& SvStream::ReadInt16(sal_Int16& r) { return ReadNumber(r); }
+SvStream& SvStream::ReadInt32(sal_Int32& r) { return ReadNumber(r); }
+SvStream& SvStream::ReadInt64(sal_Int64& r) { return ReadNumber(r); }
+
+SvStream& SvStream::ReadSChar( signed char& r )
+{
+ if (m_isIoRead && sizeof(signed char) <= m_nBufFree)
+ {
+ r = *m_pBufPos;
+ m_nBufActualPos += sizeof(signed char);
+ m_pBufPos += sizeof(signed char);
+ m_nBufFree -= sizeof(signed char);
+ }
+ else
+ ReadBytes( &r, sizeof(signed char) );
+ return *this;
+}
+
+// Special treatment for Chars due to PutBack
+
+SvStream& SvStream::ReadChar( char& r )
+{
+ if (m_isIoRead && sizeof(char) <= m_nBufFree)
+ {
+ r = *m_pBufPos;
+ m_nBufActualPos += sizeof(char);
+ m_pBufPos += sizeof(char);
+ m_nBufFree -= sizeof(char);
+ }
+ else
+ ReadBytes( &r, sizeof(char) );
+ return *this;
+}
+
+SvStream& SvStream::ReadUChar( unsigned char& r )
+{
+ if (m_isIoRead && sizeof(char) <= m_nBufFree)
+ {
+ r = *m_pBufPos;
+ m_nBufActualPos += sizeof(char);
+ m_pBufPos += sizeof(char);
+ m_nBufFree -= sizeof(char);
+ }
+ else
+ ReadBytes( &r, sizeof(char) );
+ return *this;
+}
+
+SvStream& SvStream::ReadUtf16(sal_Unicode& r) { return ReadNumber(r); }
+
+SvStream& SvStream::ReadCharAsBool( bool& r )
+{
+ if (m_isIoRead && sizeof(char) <= m_nBufFree)
+ {
+ SAL_WARN_IF(
+ *m_pBufPos > 1, "tools.stream", unsigned(*m_pBufPos) << " not 0/1");
+ r = *m_pBufPos != 0;
+ m_nBufActualPos += sizeof(char);
+ m_pBufPos += sizeof(char);
+ m_nBufFree -= sizeof(char);
+ }
+ else
+ {
+ unsigned char c;
+ if (ReadBytes(&c, 1) == 1)
+ {
+ SAL_WARN_IF(c > 1, "tools.stream", unsigned(c) << " not 0/1");
+ r = c != 0;
+ }
+ }
+ return *this;
+}
+
+SvStream& SvStream::ReadFloat(float& r)
+{
+ float n = 0;
+ readNumberWithoutSwap(n);
+ if (good())
+ {
+#if defined UNX
+ if (m_isSwap)
+ SwapFloat(n);
+#endif
+ r = n;
+ }
+ return *this;
+}
+
+SvStream& SvStream::ReadDouble(double& r)
+{
+ double n = 0;
+ readNumberWithoutSwap(n);
+ if (good())
+ {
+#if defined UNX
+ if (m_isSwap)
+ SwapDouble(n);
+#endif
+ r = n;
+ }
+ return *this;
+}
+
+SvStream& SvStream::ReadStream( SvStream& rStream )
+{
+ const sal_uInt32 cBufLen = 0x8000;
+ std::unique_ptr<char[]> pBuf( new char[ cBufLen ] );
+
+ sal_uInt32 nCount;
+ do {
+ nCount = ReadBytes( pBuf.get(), cBufLen );
+ rStream.WriteBytes( pBuf.get(), nCount );
+ } while( nCount == cBufLen );
+
+ return *this;
+}
+
+template <typename T> SvStream& SvStream::WriteNumber(T n)
+{
+ if (m_isSwap)
+ SwapNumber(n);
+ writeNumberWithoutSwap(n);
+ return *this;
+}
+
+SvStream& SvStream::WriteUInt16(sal_uInt16 v) { return WriteNumber(v); }
+SvStream& SvStream::WriteUInt32(sal_uInt32 v) { return WriteNumber(v); }
+SvStream& SvStream::WriteUInt64(sal_uInt64 v) { return WriteNumber(v); }
+SvStream& SvStream::WriteInt16(sal_Int16 v) { return WriteNumber(v); }
+SvStream& SvStream::WriteInt32(sal_Int32 v) { return WriteNumber(v); }
+SvStream& SvStream::WriteInt64(sal_Int64 v) { return WriteNumber(v); }
+
+SvStream& SvStream::WriteSChar( signed char v )
+{
+ //SDO
+ if (m_isIoWrite && sizeof(signed char) <= m_nBufFree)
+ {
+ *m_pBufPos = v;
+ m_pBufPos++; // sizeof(char);
+ m_nBufActualPos++;
+ if (m_nBufActualPos > m_nBufActualLen) // Append ?
+ m_nBufActualLen = m_nBufActualPos;
+ m_nBufFree--; // = sizeof(char);
+ m_isDirty = true;
+ }
+ else
+ WriteBytes( &v, sizeof(signed char) );
+ return *this;
+}
+
+// Special treatment for Chars due to PutBack
+
+SvStream& SvStream::WriteChar( char v )
+{
+ //SDO
+ if (m_isIoWrite && sizeof(char) <= m_nBufFree)
+ {
+ *m_pBufPos = v;
+ m_pBufPos++; // sizeof(char);
+ m_nBufActualPos++;
+ if (m_nBufActualPos > m_nBufActualLen) // Append ?
+ m_nBufActualLen = m_nBufActualPos;
+ m_nBufFree--; // = sizeof(char);
+ m_isDirty = true;
+ }
+ else
+ WriteBytes( &v, sizeof(char) );
+ return *this;
+}
+
+SvStream& SvStream::WriteUChar( unsigned char v )
+{
+//SDO
+ if (m_isIoWrite && sizeof(char) <= m_nBufFree)
+ {
+ *reinterpret_cast<unsigned char*>(m_pBufPos) = v;
+ m_pBufPos++; // = sizeof(char);
+ m_nBufActualPos++; // = sizeof(char);
+ if (m_nBufActualPos > m_nBufActualLen) // Append ?
+ m_nBufActualLen = m_nBufActualPos;
+ m_nBufFree--;
+ m_isDirty = true;
+ }
+ else
+ WriteBytes( &v, sizeof(char) );
+ return *this;
+}
+
+SvStream& SvStream::WriteUInt8( sal_uInt8 v )
+{
+ return WriteUChar(v);
+}
+
+SvStream& SvStream::WriteUnicode( sal_Unicode v )
+{
+ return WriteUInt16(v);
+}
+
+SvStream& SvStream::WriteFloat( float v )
+{
+#ifdef UNX
+ if (m_isSwap)
+ SwapFloat(v);
+#endif
+ writeNumberWithoutSwap(v);
+ return *this;
+}
+
+SvStream& SvStream::WriteDouble ( const double& r )
+{
+#if defined UNX
+ if (m_isSwap)
+ {
+ double nHelp = r;
+ SwapDouble(nHelp);
+ writeNumberWithoutSwap(nHelp);
+ return *this;
+ }
+ else
+#endif
+ {
+ writeNumberWithoutSwap(r);
+ }
+ return *this;
+}
+
+SvStream& SvStream::WriteStream( SvStream& rStream )
+{
+ const sal_uInt32 cBufLen = 0x8000;
+ std::unique_ptr<char[]> pBuf( new char[ cBufLen ] );
+ sal_uInt32 nCount;
+ do {
+ nCount = rStream.ReadBytes( pBuf.get(), cBufLen );
+ WriteBytes( pBuf.get(), nCount );
+ } while( nCount == cBufLen );
+
+ return *this;
+}
+
+sal_uInt64 SvStream::WriteStream( SvStream& rStream, sal_uInt64 nSize )
+{
+ const sal_uInt32 cBufLen = 0x8000;
+ std::unique_ptr<char[]> pBuf( new char[ cBufLen ] );
+ sal_uInt32 nCurBufLen = cBufLen;
+ sal_uInt32 nCount;
+ sal_uInt64 nWriteSize = nSize;
+
+ do
+ {
+ nCurBufLen = std::min<sal_uInt64>(nCurBufLen, nWriteSize);
+ nCount = rStream.ReadBytes(pBuf.get(), nCurBufLen);
+ WriteBytes( pBuf.get(), nCount );
+ nWriteSize -= nCount;
+ }
+ while( nWriteSize && nCount == nCurBufLen );
+
+ return nSize - nWriteSize;
+}
+
+OUString SvStream::ReadUniOrByteString( rtl_TextEncoding eSrcCharSet )
+{
+ // read UTF-16 string directly from stream ?
+ if (eSrcCharSet == RTL_TEXTENCODING_UNICODE)
+ return read_uInt32_lenPrefixed_uInt16s_ToOUString(*this);
+ return read_uInt16_lenPrefixed_uInt8s_ToOUString(*this, eSrcCharSet);
+}
+
+SvStream& SvStream::WriteUniOrByteString( std::u16string_view rStr, rtl_TextEncoding eDestCharSet )
+{
+ // write UTF-16 string directly into stream ?
+ if (eDestCharSet == RTL_TEXTENCODING_UNICODE)
+ write_uInt32_lenPrefixed_uInt16s_FromOUString(*this, rStr);
+ else
+ write_uInt16_lenPrefixed_uInt8s_FromOUString(*this, rStr, eDestCharSet);
+ return *this;
+}
+
+void SvStream::FlushBuffer()
+{
+ if (m_isDirty) // Does stream require a flush?
+ {
+ SeekPos(m_nBufFilePos);
+ if (m_nCryptMask)
+ CryptAndWriteBuffer(m_pRWBuf.get(), m_nBufActualLen);
+ else if (PutData(m_pRWBuf.get(), m_nBufActualLen) != m_nBufActualLen)
+ SetError(SVSTREAM_WRITE_ERROR);
+ m_isDirty = false;
+ }
+}
+
+std::size_t SvStream::ReadBytes( void* pData, std::size_t nCount )
+{
+ std::size_t nSaveCount = nCount;
+
+ if (!m_pRWBuf)
+ {
+ nCount = GetData( pData,nCount);
+ if (m_nCryptMask)
+ EncryptBuffer(pData, nCount);
+ m_nBufFilePos += nCount;
+ }
+ else
+ {
+ // check if block is completely within buffer
+ m_isIoRead = true;
+ m_isIoWrite = false;
+ if (nCount <= o3tl::make_unsigned(m_nBufActualLen - m_nBufActualPos))
+ {
+ // => yes
+ if (nCount != 0)
+ memcpy(pData, m_pBufPos, nCount);
+ m_nBufActualPos = m_nBufActualPos + static_cast<sal_uInt16>(nCount);
+ m_pBufPos += nCount;
+ m_nBufFree = m_nBufFree - static_cast<sal_uInt16>(nCount);
+ }
+ else
+ {
+ FlushBuffer();
+
+ // Does data block fit into buffer?
+ if (nCount > m_nBufSize)
+ {
+ // => No! Thus read directly
+ // into target area without using the buffer
+
+ m_isIoRead = false;
+
+ SeekPos(m_nBufFilePos + m_nBufActualPos);
+ m_nBufActualLen = 0;
+ m_pBufPos = m_pRWBuf.get();
+ nCount = GetData( pData, nCount );
+ if (m_nCryptMask)
+ EncryptBuffer(pData, nCount);
+ m_nBufFilePos += nCount;
+ m_nBufFilePos += m_nBufActualPos;
+ m_nBufActualPos = 0;
+ }
+ else
+ {
+ // => Yes. Fill buffer first, then copy to target area
+
+ m_nBufFilePos += m_nBufActualPos;
+ SeekPos(m_nBufFilePos);
+
+ // TODO: Typecast before GetData, sal_uInt16 nCountTmp
+ std::size_t nCountTmp = GetData( m_pRWBuf.get(), m_nBufSize );
+ if (m_nCryptMask)
+ EncryptBuffer(m_pRWBuf.get(), nCountTmp);
+ m_nBufActualLen = static_cast<sal_uInt16>(nCountTmp);
+ if( nCount > nCountTmp )
+ {
+ nCount = nCountTmp; // trim count back, EOF see below
+ }
+ memcpy( pData, m_pRWBuf.get(), nCount );
+ m_nBufActualPos = static_cast<sal_uInt16>(nCount);
+ m_pBufPos = m_pRWBuf.get() + nCount;
+ }
+ }
+ }
+ m_isEof = false;
+ m_nBufFree = m_nBufActualLen - m_nBufActualPos;
+ if (nCount != nSaveCount && m_nError != ERRCODE_IO_PENDING)
+ m_isEof = true;
+ if (nCount == nSaveCount && m_nError == ERRCODE_IO_PENDING)
+ m_nError = ERRCODE_NONE;
+ return nCount;
+}
+
+std::size_t SvStream::WriteBytes( const void* pData, std::size_t nCount )
+{
+ if( !nCount )
+ return 0;
+
+ if (!m_isWritable)
+ {
+ SetError( ERRCODE_IO_CANTWRITE );
+ return 0;
+ }
+
+ if (!m_pRWBuf)
+ {
+ if (m_nCryptMask)
+ nCount = CryptAndWriteBuffer( pData, nCount );
+ else
+ nCount = PutData( pData, nCount );
+ m_nBufFilePos += nCount;
+ return nCount;
+ }
+
+ m_isIoRead = false;
+ m_isIoWrite = true;
+ if (nCount <= o3tl::make_unsigned(m_nBufSize - m_nBufActualPos))
+ {
+ memcpy( m_pBufPos, pData, nCount );
+ m_nBufActualPos = m_nBufActualPos + static_cast<sal_uInt16>(nCount);
+ // Update length if buffer was updated
+ if (m_nBufActualPos > m_nBufActualLen)
+ m_nBufActualLen = m_nBufActualPos;
+
+ m_pBufPos += nCount;
+ m_isDirty = true;
+ }
+ else
+ {
+ FlushBuffer();
+
+ // Does data block fit into buffer?
+ if (nCount > m_nBufSize)
+ {
+ m_isIoWrite = false;
+ m_nBufFilePos += m_nBufActualPos;
+ m_nBufActualLen = 0;
+ m_nBufActualPos = 0;
+ m_pBufPos = m_pRWBuf.get();
+ SeekPos(m_nBufFilePos);
+ if (m_nCryptMask)
+ nCount = CryptAndWriteBuffer( pData, nCount );
+ else
+ nCount = PutData( pData, nCount );
+ m_nBufFilePos += nCount;
+ }
+ else
+ {
+ // Copy block to buffer
+ memcpy( m_pRWBuf.get(), pData, nCount );
+
+ // Mind the order!
+ m_nBufFilePos += m_nBufActualPos;
+ m_nBufActualPos = static_cast<sal_uInt16>(nCount);
+ m_pBufPos = m_pRWBuf.get() + nCount;
+ m_nBufActualLen = static_cast<sal_uInt16>(nCount);
+ m_isDirty = true;
+ }
+ }
+ m_nBufFree = m_nBufSize - m_nBufActualPos;
+ return nCount;
+}
+
+sal_uInt64 SvStream::Seek(sal_uInt64 const nFilePos)
+{
+ m_isIoRead = m_isIoWrite = false;
+ m_isEof = false;
+ if (!m_pRWBuf)
+ {
+ m_nBufFilePos = SeekPos( nFilePos );
+ DBG_ASSERT(Tell() == m_nBufFilePos,"Out Of Sync!");
+ return m_nBufFilePos;
+ }
+
+ // Is seek position within buffer?
+ if (nFilePos >= m_nBufFilePos && nFilePos <= (m_nBufFilePos + m_nBufActualLen))
+ {
+ m_nBufActualPos = static_cast<sal_uInt16>(nFilePos - m_nBufFilePos);
+ m_pBufPos = m_pRWBuf.get() + m_nBufActualPos;
+ // Update m_nBufFree to avoid crash upon PutBack
+ m_nBufFree = m_nBufActualLen - m_nBufActualPos;
+ }
+ else
+ {
+ FlushBuffer();
+ m_nBufActualLen = 0;
+ m_nBufActualPos = 0;
+ m_pBufPos = m_pRWBuf.get();
+ m_nBufFilePos = SeekPos( nFilePos );
+ }
+ return m_nBufFilePos + m_nBufActualPos;
+}
+
+bool checkSeek(SvStream &rSt, sal_uInt64 nOffset)
+{
+ const sal_uInt64 nMaxSeek = rSt.TellEnd();
+ return (nOffset <= nMaxSeek && rSt.Seek(nOffset) == nOffset);
+}
+
+namespace tools
+{
+bool isEmptyFileUrl(const OUString& rUrl)
+{
+ if (!comphelper::isFileUrl(rUrl))
+ {
+ return false;
+ }
+
+ SvFileStream aStream(rUrl, StreamMode::READ);
+ if (!aStream.IsOpen())
+ {
+ return false;
+ }
+
+ return aStream.remainingSize() == 0;
+}
+}
+
+//STREAM_SEEK_TO_END in some of the Seek backends is special cased to be
+//efficient, in others e.g. SotStorageStream it's really horribly slow, and in
+//those this should be overridden
+sal_uInt64 SvStream::remainingSize()
+{
+ sal_uInt64 const nCurr = Tell();
+ sal_uInt64 const nEnd = TellEnd();
+ sal_uInt64 nMaxAvailable = nEnd > nCurr ? (nEnd-nCurr) : 0;
+ return nMaxAvailable;
+}
+
+sal_uInt64 SvStream::TellEnd()
+{
+ FlushBuffer();
+ sal_uInt64 const nCurr = Tell();
+ sal_uInt64 const nEnd = Seek(STREAM_SEEK_TO_END);
+ Seek(nCurr);
+ return nEnd;
+}
+
+void SvStream::Flush()
+{
+ FlushBuffer();
+ if (m_isWritable)
+ FlushData();
+}
+
+void SvStream::RefreshBuffer()
+{
+ FlushBuffer();
+ SeekPos(m_nBufFilePos);
+ m_nBufActualLen = static_cast<sal_uInt16>(GetData( m_pRWBuf.get(), m_nBufSize ));
+ if (m_nBufActualLen && m_nError == ERRCODE_IO_PENDING)
+ m_nError = ERRCODE_NONE;
+ if (m_nCryptMask)
+ EncryptBuffer(m_pRWBuf.get(), static_cast<std::size_t>(m_nBufActualLen));
+ m_isIoRead = m_isIoWrite = false;
+}
+
+#define CRYPT_BUFSIZE 1024
+
+/// Encrypt and write
+std::size_t SvStream::CryptAndWriteBuffer( const void* pStart, std::size_t nLen)
+{
+ unsigned char pTemp[CRYPT_BUFSIZE];
+ unsigned char const * pDataPtr = static_cast<unsigned char const *>(pStart);
+ std::size_t nCount = 0;
+ std::size_t nBufCount;
+ unsigned char nMask = m_nCryptMask;
+ do
+ {
+ if( nLen >= CRYPT_BUFSIZE )
+ nBufCount = CRYPT_BUFSIZE;
+ else
+ nBufCount = nLen;
+ nLen -= nBufCount;
+ memcpy( pTemp, pDataPtr, static_cast<sal_uInt16>(nBufCount) );
+ // ******** Encrypt ********
+ for (unsigned char & rn : pTemp)
+ {
+ unsigned char aCh = rn;
+ aCh ^= nMask;
+ swapNibbles(aCh);
+ rn = aCh;
+ }
+ // *************************
+ nCount += PutData( pTemp, nBufCount );
+ pDataPtr += nBufCount;
+ }
+ while ( nLen );
+ return nCount;
+}
+
+void SvStream::EncryptBuffer(void* pStart, std::size_t nLen) const
+{
+ unsigned char* pTemp = static_cast<unsigned char*>(pStart);
+ unsigned char nMask = m_nCryptMask;
+
+ for ( std::size_t n=0; n < nLen; n++, pTemp++ )
+ {
+ unsigned char aCh = *pTemp;
+ swapNibbles(aCh);
+ aCh ^= nMask;
+ *pTemp = aCh;
+ }
+}
+
+static unsigned char implGetCryptMask(const char* pStr, sal_Int32 nLen, tools::Long nVersion)
+{
+ unsigned char nCryptMask = 0;
+
+ if (!nLen)
+ return nCryptMask;
+
+ if( nVersion <= SOFFICE_FILEFORMAT_31 )
+ {
+ while( nLen )
+ {
+ nCryptMask ^= *pStr;
+ pStr++;
+ nLen--;
+ }
+ }
+ else // BugFix #25888#
+ {
+ for( sal_Int32 i = 0; i < nLen; i++ ) {
+ nCryptMask ^= pStr[i];
+ if( nCryptMask & 0x80 ) {
+ nCryptMask <<= 1;
+ nCryptMask++;
+ }
+ else
+ nCryptMask <<= 1;
+ }
+ }
+
+ if( !nCryptMask )
+ nCryptMask = 67;
+
+ return nCryptMask;
+}
+
+void SvStream::SetCryptMaskKey(const OString& rCryptMaskKey)
+{
+ m_aCryptMaskKey = rCryptMaskKey;
+ m_nCryptMask = implGetCryptMask(m_aCryptMaskKey.getStr(),
+ m_aCryptMaskKey.getLength(), GetVersion());
+}
+
+bool SvStream::SetStreamSize(sal_uInt64 const nSize)
+{
+#ifdef DBG_UTIL
+ sal_uInt64 nFPos = Tell();
+#endif
+ sal_uInt16 nBuf = m_nBufSize;
+ SetBufferSize( 0 );
+ SetSize( nSize );
+ if (nSize < m_nBufFilePos)
+ {
+ m_nBufFilePos = nSize;
+ }
+ SetBufferSize( nBuf );
+#ifdef DBG_UTIL
+ DBG_ASSERT(Tell()==nFPos,"SetStreamSize failed");
+#endif
+ return (m_nError == ERRCODE_NONE);
+}
+
+SvStream& endl( SvStream& rStr )
+{
+ LineEnd eDelim = rStr.GetLineDelimiter();
+ if ( eDelim == LINEEND_CR )
+ rStr.WriteChar('\r');
+ else if( eDelim == LINEEND_LF )
+ rStr.WriteChar('\n');
+ else
+ rStr.WriteChar('\r').WriteChar('\n');
+ return rStr;
+}
+
+SvStream& endlu( SvStream& rStrm )
+{
+ switch ( rStrm.GetLineDelimiter() )
+ {
+ case LINEEND_CR :
+ rStrm.WriteUnicode('\r');
+ break;
+ case LINEEND_LF :
+ rStrm.WriteUnicode('\n');
+ break;
+ default:
+ rStrm.WriteUnicode('\r').WriteUnicode('\n');
+ }
+ return rStrm;
+}
+
+SvStream& endlub( SvStream& rStrm )
+{
+ if ( rStrm.GetStreamCharSet() == RTL_TEXTENCODING_UNICODE )
+ return endlu( rStrm );
+ else
+ return endl( rStrm );
+}
+
+SvMemoryStream::SvMemoryStream( void* pBuffer, std::size_t bufSize,
+ StreamMode eMode )
+{
+ if( eMode & StreamMode::WRITE )
+ m_isWritable = true;
+ else
+ m_isWritable = false;
+ nEndOfData = bufSize;
+ bOwnsData = false;
+ pBuf = static_cast<sal_uInt8 *>(pBuffer);
+ nResize = 0;
+ nSize = bufSize;
+ nPos = 0;
+ SetBufferSize( 0 );
+}
+
+SvMemoryStream::SvMemoryStream( std::size_t nInitSize, std::size_t nResizeOffset )
+{
+ m_isWritable = true;
+ bOwnsData = true;
+ nEndOfData = 0;
+ nResize = nResizeOffset;
+ nPos = 0;
+ pBuf = nullptr;
+ if( nResize != 0 && nResize < 16 )
+ nResize = 16;
+ if( nInitSize )
+ AllocateMemory( nInitSize );
+ nSize = nInitSize;
+ SetBufferSize( 64 );
+}
+
+SvMemoryStream::~SvMemoryStream()
+{
+ if( pBuf )
+ {
+ if( bOwnsData )
+ FreeMemory();
+ else
+ FlushBuffer();
+ }
+}
+
+void SvMemoryStream::SetBuffer( void* pNewBuf, std::size_t nCount,
+ std::size_t nEOF )
+{
+ SetBufferSize( 0 ); // Init buffering in the base class
+ Seek( 0 );
+ if( bOwnsData && pNewBuf != pBuf )
+ FreeMemory();
+
+ pBuf = static_cast<sal_uInt8 *>(pNewBuf);
+ nPos = 0;
+ nSize = nCount;
+ nResize = 0;
+ bOwnsData = false;
+
+ if( nEOF > nCount )
+ nEOF = nCount;
+ nEndOfData = nEOF;
+
+ ResetError();
+}
+
+std::size_t SvMemoryStream::GetData( void* pData, std::size_t nCount )
+{
+ std::size_t nMaxCount = nEndOfData-nPos;
+ if( nCount > nMaxCount )
+ nCount = nMaxCount;
+ if (nCount != 0)
+ {
+ memcpy( pData, pBuf+nPos, nCount );
+ }
+ nPos += nCount;
+ return nCount;
+}
+
+std::size_t SvMemoryStream::PutData( const void* pData, std::size_t nCount )
+{
+ if( GetError() )
+ return 0;
+
+ std::size_t nMaxCount = nSize-nPos;
+
+ // check for overflow
+ if( nCount > nMaxCount )
+ {
+ if( nResize == 0 )
+ {
+ // copy as much as possible
+ nCount = nMaxCount;
+ SetError( SVSTREAM_OUTOFMEMORY );
+ }
+ else
+ {
+ tools::Long nNewResize;
+ if( nSize && nSize > nResize )
+ nNewResize = nSize;
+ else
+ nNewResize = nResize;
+
+ if( (nCount-nMaxCount) < nResize )
+ {
+ // lacking memory is smaller than nResize,
+ // resize accordingly
+ if( !ReAllocateMemory( nNewResize) )
+ {
+ nCount = 0;
+ SetError( SVSTREAM_WRITE_ERROR );
+ }
+ }
+ else
+ {
+ // lacking memory is larger than nResize,
+ // resize by (nCount-nMaxCount) + resize offset
+ if( !ReAllocateMemory( nCount-nMaxCount+nNewResize ) )
+ {
+ nCount = 0;
+ SetError( SVSTREAM_WRITE_ERROR );
+ }
+ }
+ }
+ }
+ assert(pBuf && "Possibly Reallocate failed");
+ memcpy( pBuf+nPos, pData, nCount);
+
+ nPos += nCount;
+ if( nPos > nEndOfData )
+ nEndOfData = nPos;
+ return nCount;
+}
+
+sal_uInt64 SvMemoryStream::SeekPos(sal_uInt64 const nNewPos)
+{
+ // nEndOfData: First position in stream not allowed to read from
+ // nSize: Size of allocated buffer
+
+ // check if a truncated STREAM_SEEK_TO_END was passed
+ assert(nNewPos != SAL_MAX_UINT32);
+ if( nNewPos < nEndOfData )
+ nPos = nNewPos;
+ else if( nNewPos == STREAM_SEEK_TO_END )
+ nPos = nEndOfData;
+ else
+ {
+ if( nNewPos >= nSize ) // Does buffer need extension?
+ {
+ if( nResize ) // Is extension possible?
+ {
+ tools::Long nDiff = static_cast<tools::Long>(nNewPos - nSize + 1);
+ nDiff += static_cast<tools::Long>(nResize);
+ ReAllocateMemory( nDiff );
+ nPos = nNewPos;
+ nEndOfData = nNewPos;
+ }
+ else // Extension not possible, set pos to end of data
+ {
+ // SetError( SVSTREAM_OUTOFMEMORY );
+ nPos = nEndOfData;
+ }
+ }
+ else // Expand buffer size
+ {
+ nPos = nNewPos;
+ nEndOfData = nNewPos;
+ }
+ }
+ return nPos;
+}
+
+void SvMemoryStream::FlushData()
+{
+}
+
+void SvMemoryStream::ResetError()
+{
+ SvStream::ClearError();
+}
+
+void SvMemoryStream::AllocateMemory( std::size_t nNewSize )
+{
+ pBuf = new sal_uInt8[nNewSize];
+}
+
+// (using Bozo algorithm)
+bool SvMemoryStream::ReAllocateMemory( tools::Long nDiff )
+{
+ if (!m_isWritable || !bOwnsData)
+ return false;
+
+ bool bRetVal = false;
+ tools::Long nTemp = static_cast<tools::Long>(nSize);
+ nTemp += nDiff;
+ std::size_t nNewSize = static_cast<std::size_t>(nTemp);
+
+ if( nNewSize )
+ {
+ sal_uInt8* pNewBuf = new sal_uInt8[nNewSize];
+
+ bRetVal = true; // Success!
+ if( nNewSize < nSize ) // Are we shrinking?
+ {
+ memcpy( pNewBuf, pBuf, nNewSize );
+ if( nPos > nNewSize )
+ nPos = 0;
+ if( nEndOfData >= nNewSize )
+ nEndOfData = nNewSize-1;
+ }
+ else
+ {
+ if (nSize != 0)
+ {
+ memcpy( pNewBuf, pBuf, nSize );
+ }
+ memset(pNewBuf + nSize, 0x00, nNewSize - nSize);
+ }
+
+ FreeMemory();
+
+ pBuf = pNewBuf;
+ nSize = nNewSize;
+ }
+ else
+ {
+ bRetVal = true;
+ FreeMemory();
+ pBuf = nullptr;
+ nSize = 0;
+ nEndOfData = 0;
+ nPos = 0;
+ }
+
+ return bRetVal;
+}
+
+void SvMemoryStream::FreeMemory()
+{
+ assert(bOwnsData);
+ if (bOwnsData)
+ {
+ delete[] pBuf;
+ pBuf = nullptr;
+ }
+}
+
+void* SvMemoryStream::SwitchBuffer()
+{
+ FlushBuffer();
+ if( !bOwnsData )
+ return nullptr;
+ Seek( STREAM_SEEK_TO_BEGIN );
+
+ void* pRetVal = pBuf;
+ pBuf = nullptr;
+ nEndOfData = 0;
+ nResize = 64;
+ nPos = 0;
+
+ ResetError();
+
+ std::size_t nInitSize = 512;
+ AllocateMemory(nInitSize);
+ nSize = nInitSize;
+
+ SetBufferSize( 64 );
+ return pRetVal;
+}
+
+void SvMemoryStream::SetSize(sal_uInt64 const nNewSize)
+{
+ if (!m_isWritable)
+ {
+ SetError(SVSTREAM_INVALID_HANDLE);
+ return;
+ }
+
+ tools::Long nDiff = static_cast<tools::Long>(nNewSize) - static_cast<tools::Long>(nSize);
+ ReAllocateMemory( nDiff );
+}
+
+void SvMemoryStream::MakeReadOnly()
+{
+ FlushBuffer();
+ m_isWritable = false;
+ nResize = 0;
+ SetBufferSize( 0 );
+}
+
+// Create an OString of nLen bytes from rStream
+// coverity[ +taint_sanitize ]
+OString read_uInt8s_ToOString(SvStream& rStrm, std::size_t nLen)
+{
+ rtl_String *pStr = nullptr;
+ if (nLen)
+ {
+ nLen = std::min<std::size_t>(nLen, SAL_MAX_INT32);
+ //limit allocation to size of file, but + 1 to set eof state
+ nLen = std::min<sal_uInt64>(nLen, rStrm.remainingSize() + 1);
+ //alloc a (ref-count 1) rtl_String of the desired length.
+ //rtl_String's buffer is uninitialized, except for null termination
+ pStr = rtl_string_alloc(sal::static_int_cast<sal_Int32>(nLen));
+ SAL_WARN_IF(!pStr, "tools.stream", "allocation failed");
+ if (pStr)
+ {
+ std::size_t nWasRead = rStrm.ReadBytes(pStr->buffer, nLen);
+ if (nWasRead != nLen)
+ {
+ //on (typically unlikely) short read set length to what we could
+ //read, and null terminate. Excess buffer capacity remains of
+ //course, could create a (true) replacement OString if it matters.
+ pStr->length = sal::static_int_cast<sal_Int32>(nWasRead);
+ pStr->buffer[pStr->length] = 0;
+ }
+ }
+ }
+
+ //take ownership of buffer and return, otherwise return empty string
+ return pStr ? OString(pStr, SAL_NO_ACQUIRE) : OString();
+}
+
+// Create an OUString of nLen sal_Unicode code units from rStream
+// coverity[ +taint_sanitize ]
+OUString read_uInt16s_ToOUString(SvStream& rStrm, std::size_t nLen)
+{
+ rtl_uString *pStr = nullptr;
+ if (nLen)
+ {
+ nLen = std::min<std::size_t>(nLen, SAL_MAX_INT32);
+ //limit allocation to size of file, but + 1 to set eof state
+ nLen = o3tl::sanitizing_min<sal_uInt64>(nLen, (rStrm.remainingSize() + 2) / 2);
+ //alloc a (ref-count 1) rtl_uString of the desired length.
+ //rtl_String's buffer is uninitialized, except for null termination
+ pStr = rtl_uString_alloc(sal::static_int_cast<sal_Int32>(nLen));
+ SAL_WARN_IF(!pStr, "tools.stream", "allocation failed");
+ if (pStr)
+ {
+ std::size_t nWasRead = rStrm.ReadBytes(pStr->buffer, nLen*2)/2;
+ if (nWasRead != nLen)
+ {
+ //on (typically unlikely) short read set length to what we could
+ //read, and null terminate. Excess buffer capacity remains of
+ //course, could create a (true) replacement OUString if it matters.
+ pStr->length = sal::static_int_cast<sal_Int32>(nWasRead);
+ pStr->buffer[pStr->length] = 0;
+ }
+ if (rStrm.IsEndianSwap())
+ {
+ for (sal_Int32 i = 0; i < pStr->length; ++i)
+ pStr->buffer[i] = OSL_SWAPWORD(pStr->buffer[i]);
+ }
+ }
+ }
+
+ // take ownership of buffer and return, otherwise return empty string
+ // coverity[tainted_data] - unhelpful untrusted loop bound
+ return pStr ? OUString(pStr, SAL_NO_ACQUIRE) : OUString();
+}
+
+namespace
+{
+ template <typename T, typename O> T tmpl_convertLineEnd(const T &rIn, LineEnd eLineEnd)
+ {
+ // Determine linebreaks and compute length
+ bool bConvert = false; // Needs conversion
+ sal_Int32 nStrLen = rIn.getLength();
+ sal_Int32 nLineEndLen = (eLineEnd == LINEEND_CRLF) ? 2 : 1;
+ sal_Int32 nLen = 0; // Target length
+ sal_Int32 i = 0; // Source counter
+
+ while (i < nStrLen)
+ {
+ // \r or \n causes linebreak
+ if ( (rIn[i] == '\r') || (rIn[i] == '\n') )
+ {
+ nLen = nLen + nLineEndLen;
+
+ // If set already, skip expensive test
+ if ( !bConvert )
+ {
+ // Do we need to convert?
+ if ( ((eLineEnd != LINEEND_LF) && (rIn[i] == '\n')) ||
+ ((eLineEnd == LINEEND_CRLF) && (i+1) < nStrLen && (rIn[i+1] != '\n')) ||
+ ((eLineEnd == LINEEND_LF) &&
+ ((rIn[i] == '\r') || ((i+1) < nStrLen && rIn[i+1] == '\r'))) ||
+ ((eLineEnd == LINEEND_CR) &&
+ ((rIn[i] == '\n') || ((i+1) < nStrLen && rIn[i+1] == '\n'))) )
+ bConvert = true;
+ }
+
+ // skip char if \r\n or \n\r
+ if ( (i+1) < nStrLen && ((rIn[i+1] == '\r') || (rIn[i+1] == '\n')) &&
+ (rIn[i] != rIn[i+1]) )
+ ++i;
+ }
+ else
+ ++nLen;
+ ++i;
+ }
+
+ if (!bConvert)
+ return rIn;
+
+ // convert linebreaks, insert string
+ O aNewData(nLen);
+ i = 0;
+ while (i < nStrLen)
+ {
+ // \r or \n causes linebreak
+ if ( (rIn[i] == '\r') || (rIn[i] == '\n') )
+ {
+ if ( eLineEnd == LINEEND_CRLF )
+ {
+ aNewData.append('\r');
+ aNewData.append('\n');
+ }
+ else
+ {
+ if ( eLineEnd == LINEEND_CR )
+ aNewData.append('\r');
+ else
+ aNewData.append('\n');
+ }
+
+ if ( (i+1) < nStrLen && ((rIn[i+1] == '\r') || (rIn[i+1] == '\n')) &&
+ (rIn[i] != rIn[i+1]) )
+ ++i;
+ }
+ else
+ {
+ aNewData.append(rIn[i]);
+ }
+
+ ++i;
+ }
+
+ return aNewData.makeStringAndClear();
+ }
+}
+
+OString convertLineEnd(const OString &rIn, LineEnd eLineEnd)
+{
+ return tmpl_convertLineEnd<OString, OStringBuffer>(rIn, eLineEnd);
+}
+
+OUString convertLineEnd(const OUString &rIn, LineEnd eLineEnd)
+{
+ return tmpl_convertLineEnd<OUString, OUStringBuffer>(rIn, eLineEnd);
+}
+
+std::size_t write_uInt32_lenPrefixed_uInt16s_FromOUString(SvStream& rStrm,
+ std::u16string_view rStr)
+{
+ std::size_t nWritten = 0;
+ sal_uInt32 nUnits = std::min<std::size_t>(rStr.size(), std::numeric_limits<sal_uInt32>::max());
+ SAL_WARN_IF(static_cast<std::size_t>(nUnits) != static_cast<std::size_t>(rStr.size()),
+ "tools.stream",
+ "string too long for prefix count to fit in output type");
+ rStrm.WriteUInt32(nUnits);
+ if (rStrm.good())
+ {
+ nWritten += sizeof(sal_uInt32);
+ nWritten += write_uInt16s_FromOUString(rStrm, rStr, nUnits);
+ }
+ return nWritten;
+}
+
+std::size_t write_uInt16_lenPrefixed_uInt16s_FromOUString(SvStream& rStrm,
+ std::u16string_view rStr)
+{
+ std::size_t nWritten = 0;
+ sal_uInt16 nUnits = std::min<std::size_t>(rStr.size(), std::numeric_limits<sal_uInt16>::max());
+ SAL_WARN_IF(nUnits != rStr.size(),
+ "tools.stream",
+ "string too long for prefix count to fit in output type");
+ rStrm.WriteUInt16(nUnits);
+ if (rStrm.good())
+ {
+ nWritten += sizeof(nUnits);
+ nWritten += write_uInt16s_FromOUString(rStrm, rStr, nUnits);
+ }
+ return nWritten;
+}
+
+std::size_t write_uInt16_lenPrefixed_uInt8s_FromOString(SvStream& rStrm,
+ std::string_view rStr)
+{
+ std::size_t nWritten = 0;
+ sal_uInt16 nUnits = std::min<std::size_t>(rStr.size(), std::numeric_limits<sal_uInt16>::max());
+ SAL_WARN_IF(static_cast<std::size_t>(nUnits) != static_cast<std::size_t>(rStr.size()),
+ "tools.stream",
+ "string too long for sal_uInt16 count to fit in output type");
+ rStrm.WriteUInt16( nUnits );
+ if (rStrm.good())
+ {
+ nWritten += sizeof(sal_uInt16);
+ nWritten += write_uInt8s_FromOString(rStrm, rStr, nUnits);
+ }
+ return nWritten;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/stream/strmunx.cxx b/tools/source/stream/strmunx.cxx
new file mode 100644
index 0000000000..881d7a28c0
--- /dev/null
+++ b/tools/source/stream/strmunx.cxx
@@ -0,0 +1,476 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <tools/stream.hxx>
+#include <map>
+
+#include <mutex>
+#include <osl/thread.h>
+#include <sal/log.hxx>
+
+#include <osl/file.hxx>
+#include <osl/detail/file.h>
+
+using namespace osl;
+
+// InternalLock ----------------------------------------------------------------
+
+namespace {
+
+std::mutex& LockMutex()
+{
+ static std::mutex SINGLETON;
+ return SINGLETON;
+}
+
+std::map<SvFileStream const *, osl::DirectoryItem> gLocks;
+
+bool lockFile( const SvFileStream* pStream )
+{
+ osl::DirectoryItem aItem;
+ if (osl::DirectoryItem::get( pStream->GetFileName(), aItem) != osl::FileBase::E_None )
+ {
+ SAL_INFO("tools.stream", "Failed to lookup stream for locking");
+ return true;
+ }
+
+ osl::FileStatus aStatus( osl_FileStatus_Mask_Type );
+ if ( aItem.getFileStatus( aStatus ) != osl::FileBase::E_None )
+ {
+ SAL_INFO("tools.stream", "Failed to stat stream for locking");
+ return true;
+ }
+ if( aStatus.getFileType() == osl::FileStatus::Directory )
+ return true;
+
+ std::unique_lock aGuard( LockMutex() );
+ for( const auto& [rLockStream, rLockItem] : gLocks )
+ {
+ if( aItem.isIdenticalTo( rLockItem ) )
+ {
+ StreamMode nLockMode = rLockStream->GetStreamMode();
+ StreamMode nNewMode = pStream->GetStreamMode();
+ bool bDenyByOptions = (nLockMode & StreamMode::SHARE_DENYALL) ||
+ ( (nLockMode & StreamMode::SHARE_DENYWRITE) && (nNewMode & StreamMode::WRITE) ) ||
+ ( (nLockMode & StreamMode::SHARE_DENYREAD) && (nNewMode & StreamMode::READ) );
+
+ if( bDenyByOptions )
+ {
+ return false; // file is already locked
+ }
+ }
+ }
+ gLocks[pStream] = aItem;
+ return true;
+}
+
+void unlockFile( SvFileStream const * pStream )
+{
+ std::unique_lock aGuard( LockMutex() );
+ gLocks.erase(pStream);
+}
+
+}
+
+static ErrCode GetSvError( int nErrno )
+{
+ static struct { int nErr; ErrCode sv; } const errArr[] =
+ {
+ { 0, ERRCODE_NONE },
+ { EACCES, SVSTREAM_ACCESS_DENIED },
+ { EBADF, SVSTREAM_INVALID_HANDLE },
+#if defined(NETBSD) || \
+ defined(FREEBSD) || defined(MACOSX) || defined(OPENBSD) || \
+ defined(__FreeBSD_kernel__) || defined(DRAGONFLY) || \
+ defined(IOS) || defined(HAIKU)
+ { EDEADLK, SVSTREAM_LOCKING_VIOLATION },
+#else
+ { EDEADLOCK, SVSTREAM_LOCKING_VIOLATION },
+#endif
+ { EINVAL, SVSTREAM_INVALID_PARAMETER },
+ { EMFILE, SVSTREAM_TOO_MANY_OPEN_FILES },
+ { ENFILE, SVSTREAM_TOO_MANY_OPEN_FILES },
+ { ENOENT, SVSTREAM_FILE_NOT_FOUND },
+ { EPERM, SVSTREAM_ACCESS_DENIED },
+ { EROFS, SVSTREAM_ACCESS_DENIED },
+ { EAGAIN, SVSTREAM_LOCKING_VIOLATION },
+ { EISDIR, SVSTREAM_PATH_NOT_FOUND },
+ { ELOOP, SVSTREAM_PATH_NOT_FOUND },
+#if !defined(NETBSD) && !defined (FREEBSD) && \
+ !defined(MACOSX) && !defined(OPENBSD) && !defined(__FreeBSD_kernel__) && \
+ !defined(DRAGONFLY)
+ { EMULTIHOP, SVSTREAM_PATH_NOT_FOUND },
+ { ENOLINK, SVSTREAM_PATH_NOT_FOUND },
+#endif
+ { ENOTDIR, SVSTREAM_PATH_NOT_FOUND },
+ { ETXTBSY, SVSTREAM_ACCESS_DENIED },
+ { EEXIST, SVSTREAM_CANNOT_MAKE },
+ { ENOSPC, SVSTREAM_DISK_FULL },
+ { int(0xFFFF), SVSTREAM_GENERALERROR }
+ };
+
+ ErrCode nRetVal = SVSTREAM_GENERALERROR; // default error
+ int i=0;
+ do
+ {
+ if ( errArr[i].nErr == nErrno )
+ {
+ nRetVal = errArr[i].sv;
+ break;
+ }
+ ++i;
+ }
+ while( errArr[i].nErr != 0xFFFF );
+ return nRetVal;
+}
+
+static ErrCode GetSvError( oslFileError nErrno )
+{
+ static struct { oslFileError nErr; ErrCode sv; } const errArr[] =
+ {
+ { osl_File_E_None, ERRCODE_NONE },
+ { osl_File_E_ACCES, SVSTREAM_ACCESS_DENIED },
+ { osl_File_E_BADF, SVSTREAM_INVALID_HANDLE },
+ { osl_File_E_DEADLK, SVSTREAM_LOCKING_VIOLATION },
+ { osl_File_E_INVAL, SVSTREAM_INVALID_PARAMETER },
+ { osl_File_E_MFILE, SVSTREAM_TOO_MANY_OPEN_FILES },
+ { osl_File_E_NFILE, SVSTREAM_TOO_MANY_OPEN_FILES },
+ { osl_File_E_NOENT, SVSTREAM_FILE_NOT_FOUND },
+ { osl_File_E_PERM, SVSTREAM_ACCESS_DENIED },
+ { osl_File_E_ROFS, SVSTREAM_ACCESS_DENIED },
+ { osl_File_E_AGAIN, SVSTREAM_LOCKING_VIOLATION },
+ { osl_File_E_ISDIR, SVSTREAM_PATH_NOT_FOUND },
+ { osl_File_E_LOOP, SVSTREAM_PATH_NOT_FOUND },
+ { osl_File_E_MULTIHOP, SVSTREAM_PATH_NOT_FOUND },
+ { osl_File_E_NOLINK, SVSTREAM_PATH_NOT_FOUND },
+ { osl_File_E_NOTDIR, SVSTREAM_PATH_NOT_FOUND },
+ { osl_File_E_EXIST, SVSTREAM_CANNOT_MAKE },
+ { osl_File_E_NOSPC, SVSTREAM_DISK_FULL },
+ { oslFileError(0xFFFF), SVSTREAM_GENERALERROR }
+ };
+
+ ErrCode nRetVal = SVSTREAM_GENERALERROR; // default error
+ int i=0;
+ do
+ {
+ if ( errArr[i].nErr == nErrno )
+ {
+ nRetVal = errArr[i].sv;
+ break;
+ }
+ ++i;
+ }
+ while( errArr[i].nErr != oslFileError(0xFFFF) );
+ return nRetVal;
+}
+
+SvFileStream::SvFileStream( const OUString& rFileName, StreamMode nOpenMode )
+{
+ bIsOpen = false;
+ m_isWritable = false;
+
+ SetBufferSize( 1024 );
+ // convert URL to SystemPath, if necessary
+ OUString aSystemFileName;
+ if( FileBase::getSystemPathFromFileURL( rFileName , aSystemFileName )
+ != FileBase::E_None )
+ {
+ aSystemFileName = rFileName;
+ }
+ Open( aSystemFileName, nOpenMode );
+}
+
+SvFileStream::SvFileStream()
+{
+ bIsOpen = false;
+ m_isWritable = false;
+ SetBufferSize( 1024 );
+}
+
+SvFileStream::~SvFileStream()
+{
+ Close();
+}
+
+std::size_t SvFileStream::GetData( void* pData, std::size_t nSize )
+{
+ SAL_INFO("tools", OString::number(static_cast<sal_Int64>(nSize)) << " Bytes from " << aFilename);
+
+ sal_uInt64 nRead = 0;
+ if ( IsOpen() )
+ {
+ oslFileError rc = osl_readFile(mxFileHandle,pData,static_cast<sal_uInt64>(nSize),&nRead);
+ if ( rc != osl_File_E_None )
+ {
+ SetError( ::GetSvError( rc ));
+ return -1;
+ }
+ }
+ return static_cast<std::size_t>(nRead);
+}
+
+std::size_t SvFileStream::PutData( const void* pData, std::size_t nSize )
+{
+ SAL_INFO("tools", OString::number(static_cast<sal_Int64>(nSize)) << " Bytes to " << aFilename);
+
+ sal_uInt64 nWrite = 0;
+ if ( IsOpen() )
+ {
+ oslFileError rc = osl_writeFile(mxFileHandle,pData,static_cast<sal_uInt64>(nSize),&nWrite);
+ if ( rc != osl_File_E_None )
+ {
+ SetError( ::GetSvError( rc ) );
+ return -1;
+ }
+ else if( !nWrite )
+ SetError( SVSTREAM_DISK_FULL );
+ }
+ return static_cast<std::size_t>(nWrite);
+}
+
+sal_uInt64 SvFileStream::SeekPos(sal_uInt64 const nPos)
+{
+ // check if a truncated STREAM_SEEK_TO_END was passed
+ assert(nPos != sal_uInt64(sal_uInt32(STREAM_SEEK_TO_END)));
+ if ( IsOpen() )
+ {
+ oslFileError rc;
+ sal_uInt64 nNewPos;
+ if ( nPos != STREAM_SEEK_TO_END )
+ rc = osl_setFilePos( mxFileHandle, osl_Pos_Absolut, nPos );
+ else
+ rc = osl_setFilePos( mxFileHandle, osl_Pos_End, 0 );
+
+ if ( rc != osl_File_E_None )
+ {
+ SetError( SVSTREAM_SEEK_ERROR );
+ return 0;
+ }
+ if ( nPos != STREAM_SEEK_TO_END )
+ return nPos;
+ osl_getFilePos( mxFileHandle, &nNewPos );
+ return nNewPos;
+ }
+ SetError( SVSTREAM_GENERALERROR );
+ return 0;
+}
+
+void SvFileStream::FlushData()
+{
+ auto rc = osl_syncFile(mxFileHandle);
+ if (rc != osl_File_E_None)
+ SetError( ::GetSvError( rc ));
+ }
+
+bool SvFileStream::LockFile()
+{
+ int nLockMode = 0;
+
+ if ( ! IsOpen() )
+ return false;
+
+ if (m_eStreamMode & StreamMode::SHARE_DENYALL)
+ {
+ if (m_isWritable)
+ nLockMode = F_WRLCK;
+ else
+ nLockMode = F_RDLCK;
+ }
+
+ if (m_eStreamMode & StreamMode::SHARE_DENYREAD)
+ {
+ if (m_isWritable)
+ nLockMode = F_WRLCK;
+ else
+ {
+ SetError(SVSTREAM_LOCKING_VIOLATION);
+ return false;
+ }
+ }
+
+ if (m_eStreamMode & StreamMode::SHARE_DENYWRITE)
+ {
+ if (m_isWritable)
+ nLockMode = F_WRLCK;
+ else
+ nLockMode = F_RDLCK;
+ }
+
+ if (!nLockMode)
+ return true;
+
+ if( !lockFile( this ) )
+ {
+ SAL_WARN("tools.stream", "InternalLock on " << aFilename << "failed");
+ return false;
+ }
+
+ return true;
+}
+
+void SvFileStream::UnlockFile()
+{
+ if ( ! IsOpen() )
+ return;
+
+ unlockFile( this );
+}
+
+void SvFileStream::Open( const OUString& rFilename, StreamMode nOpenMode )
+{
+ sal_uInt32 uFlags;
+ oslFileHandle nHandleTmp;
+
+ Close();
+ errno = 0;
+ m_eStreamMode = nOpenMode;
+ m_eStreamMode &= ~StreamMode::TRUNC; // don't truncate on reopen
+
+ aFilename = rFilename;
+
+ SAL_INFO("tools", aFilename);
+
+ OUString aFileURL;
+ osl::DirectoryItem aItem;
+ osl::FileStatus aStatus( osl_FileStatus_Mask_Type | osl_FileStatus_Mask_LinkTargetURL );
+
+ // FIXME: we really need to switch to a pure URL model ...
+ if ( osl::File::getFileURLFromSystemPath( aFilename, aFileURL ) != osl::FileBase::E_None )
+ aFileURL = aFilename;
+
+ // don't both stat()ing a temporary file, unnecessary
+ bool bStatValid = true;
+ if (!(nOpenMode & StreamMode::TEMPORARY))
+ {
+ bStatValid = ( osl::DirectoryItem::get( aFileURL, aItem) == osl::FileBase::E_None &&
+ aItem.getFileStatus( aStatus ) == osl::FileBase::E_None );
+
+ // SvFileStream can't open a directory
+ if( bStatValid && aStatus.getFileType() == osl::FileStatus::Directory )
+ {
+ SetError( ::GetSvError( EISDIR ) );
+ return;
+ }
+ }
+
+ if ( !( nOpenMode & StreamMode::WRITE ) )
+ uFlags = osl_File_OpenFlag_Read;
+ else if ( !( nOpenMode & StreamMode::READ ) )
+ uFlags = osl_File_OpenFlag_Write;
+ else
+ uFlags = osl_File_OpenFlag_Read | osl_File_OpenFlag_Write;
+
+ // Fix (MDA, 18.01.95): Don't open with O_CREAT upon RD_ONLY
+ // Important for Read-Only-Filesystems (e.g, CDROM)
+ if ( (!( nOpenMode & StreamMode::NOCREATE )) && ( uFlags != osl_File_OpenFlag_Read ) )
+ uFlags |= osl_File_OpenFlag_Create;
+ if ( nOpenMode & StreamMode::TRUNC )
+ uFlags |= osl_File_OpenFlag_Trunc;
+
+ uFlags |= osl_File_OpenFlag_NoExcl | osl_File_OpenFlag_NoLock;
+
+ if ( nOpenMode & StreamMode::WRITE)
+ {
+ if ( nOpenMode & StreamMode::COPY_ON_SYMLINK )
+ {
+ if ( bStatValid && aStatus.getFileType() == osl::FileStatus::Link &&
+ aStatus.getLinkTargetURL().getLength() > 0 )
+ {
+ // delete the symbolic link, and replace it with the contents of the link
+ if (osl::File::remove( aFileURL ) == osl::FileBase::E_None )
+ {
+ File::copy( aStatus.getLinkTargetURL(), aFileURL );
+ SAL_INFO("tools.stream",
+ "Removing link and replacing with file contents (" <<
+ aStatus.getLinkTargetURL() << ") -> (" << aFileURL << ").");
+ }
+ }
+ }
+ }
+
+ oslFileError rc = osl_openFile( aFileURL.pData, &nHandleTmp, uFlags );
+ if ( rc != osl_File_E_None )
+ {
+ if ( uFlags & osl_File_OpenFlag_Write )
+ {
+ // Change to read-only
+ uFlags &= ~osl_File_OpenFlag_Write;
+ rc = osl_openFile( aFileURL.pData, &nHandleTmp, uFlags );
+ }
+ }
+ if ( rc == osl_File_E_None )
+ {
+ mxFileHandle = nHandleTmp;
+ bIsOpen = true;
+ if ( uFlags & osl_File_OpenFlag_Write )
+ m_isWritable = true;
+
+ if ( !LockFile() ) // whole file
+ {
+ osl_closeFile( nHandleTmp );
+ bIsOpen = false;
+ m_isWritable = false;
+ mxFileHandle = nullptr;
+ }
+ }
+ else
+ SetError( ::GetSvError( rc ) );
+}
+
+void SvFileStream::Close()
+{
+ UnlockFile();
+
+ if ( IsOpen() )
+ {
+ SAL_INFO("tools", "Closing " << aFilename);
+ FlushBuffer();
+ osl_closeFile( mxFileHandle );
+ mxFileHandle = nullptr;
+ }
+
+ bIsOpen = false;
+ m_isWritable = false;
+ SvStream::ClearBuffer();
+ SvStream::ClearError();
+}
+
+/// set filepointer to beginning of file
+void SvFileStream::ResetError()
+{
+ SvStream::ClearError();
+}
+
+void SvFileStream::SetSize (sal_uInt64 const nSize)
+{
+ if (IsOpen())
+ {
+ oslFileError rc = osl_setFileSize( mxFileHandle, nSize );
+ if (rc != osl_File_E_None )
+ {
+ SetError ( ::GetSvError( rc ));
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/stream/strmwnt.cxx b/tools/source/stream/strmwnt.cxx
new file mode 100644
index 0000000000..d7d3a73ed2
--- /dev/null
+++ b/tools/source/stream/strmwnt.cxx
@@ -0,0 +1,415 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+// TODO: StreamMode <-> AllocateMemory
+
+#include <string.h>
+#include <limits.h>
+
+#ifdef _WIN32
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#endif
+
+#include <osl/thread.h>
+#include <tools/stream.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <osl/file.hxx>
+using namespace osl;
+
+static ErrCode GetSvError( DWORD nWntError )
+{
+ static struct { DWORD wnt; ErrCode sv; } errArr[] =
+ {
+ { ERROR_SUCCESS, ERRCODE_NONE },
+ { ERROR_ACCESS_DENIED, SVSTREAM_ACCESS_DENIED },
+ { ERROR_ACCOUNT_DISABLED, SVSTREAM_ACCESS_DENIED },
+ { ERROR_ACCOUNT_EXPIRED, SVSTREAM_ACCESS_DENIED },
+ { ERROR_ACCOUNT_RESTRICTION, SVSTREAM_ACCESS_DENIED },
+ { ERROR_ATOMIC_LOCKS_NOT_SUPPORTED, SVSTREAM_INVALID_PARAMETER },
+ { ERROR_BAD_PATHNAME, SVSTREAM_PATH_NOT_FOUND },
+ // Filename too long
+ { ERROR_BUFFER_OVERFLOW, SVSTREAM_INVALID_PARAMETER },
+ { ERROR_DIRECTORY, SVSTREAM_INVALID_PARAMETER },
+ { ERROR_DRIVE_LOCKED, SVSTREAM_LOCKING_VIOLATION },
+ { ERROR_FILE_NOT_FOUND, SVSTREAM_FILE_NOT_FOUND },
+ { ERROR_FILENAME_EXCED_RANGE, SVSTREAM_INVALID_PARAMETER },
+ { ERROR_INVALID_ACCESS, SVSTREAM_INVALID_ACCESS },
+ { ERROR_INVALID_DRIVE, SVSTREAM_PATH_NOT_FOUND },
+ { ERROR_INVALID_HANDLE, SVSTREAM_INVALID_HANDLE },
+ { ERROR_INVALID_NAME, SVSTREAM_PATH_NOT_FOUND },
+ { ERROR_INVALID_PARAMETER, SVSTREAM_INVALID_PARAMETER },
+ { ERROR_IS_SUBST_PATH, SVSTREAM_INVALID_PARAMETER },
+ { ERROR_IS_SUBST_TARGET, SVSTREAM_INVALID_PARAMETER },
+ { ERROR_LOCK_FAILED, SVSTREAM_LOCKING_VIOLATION },
+ { ERROR_LOCK_VIOLATION, SVSTREAM_LOCKING_VIOLATION },
+ { ERROR_NEGATIVE_SEEK, SVSTREAM_SEEK_ERROR },
+ { ERROR_PATH_NOT_FOUND, SVSTREAM_PATH_NOT_FOUND },
+ { ERROR_READ_FAULT, SVSTREAM_READ_ERROR },
+ { ERROR_SEEK, SVSTREAM_SEEK_ERROR },
+ { ERROR_SEEK_ON_DEVICE, SVSTREAM_SEEK_ERROR },
+ { ERROR_SHARING_BUFFER_EXCEEDED,SVSTREAM_SHARE_BUFF_EXCEEDED },
+ { ERROR_SHARING_PAUSED, SVSTREAM_SHARING_VIOLATION },
+ { ERROR_SHARING_VIOLATION, SVSTREAM_SHARING_VIOLATION },
+ { ERROR_TOO_MANY_OPEN_FILES, SVSTREAM_TOO_MANY_OPEN_FILES },
+ { ERROR_WRITE_FAULT, SVSTREAM_WRITE_ERROR },
+ { ERROR_WRITE_PROTECT, SVSTREAM_ACCESS_DENIED },
+ { ERROR_DISK_FULL, SVSTREAM_DISK_FULL },
+
+ { DWORD(0xFFFFFFFF), SVSTREAM_GENERALERROR }
+ };
+
+ ErrCode nRetVal = SVSTREAM_GENERALERROR; // default error
+ int i=0;
+ do
+ {
+ if( errArr[i].wnt == nWntError )
+ {
+ nRetVal = errArr[i].sv;
+ break;
+ }
+ i++;
+ } while( errArr[i].wnt != DWORD(0xFFFFFFFF) );
+ return nRetVal;
+}
+
+SvFileStream::SvFileStream( const OUString& rFileName, StreamMode nMode )
+{
+ bIsOpen = false;
+ nLockCounter = 0;
+ m_isWritable = false;
+
+ SetBufferSize( 8192 );
+ // convert URL to SystemPath, if necessary
+ OUString aFileName;
+
+ if ( FileBase::getSystemPathFromFileURL( rFileName, aFileName ) != FileBase::E_None )
+ aFileName = rFileName;
+ Open( aFileName, nMode );
+}
+
+SvFileStream::SvFileStream()
+{
+ bIsOpen = false;
+ nLockCounter = 0;
+ m_isWritable = false;
+
+ SetBufferSize( 8192 );
+}
+
+SvFileStream::~SvFileStream()
+{
+ Close();
+}
+
+/// Does not check for EOF, makes isEof callable
+std::size_t SvFileStream::GetData( void* pData, std::size_t nSize )
+{
+ DWORD nCount = 0;
+ if( IsOpen() )
+ {
+ bool bResult = ReadFile(mxFileHandle,pData,nSize,&nCount,nullptr);
+ if( !bResult )
+ {
+ std::size_t nTestError = GetLastError();
+ SetError(::GetSvError( nTestError ) );
+ }
+ }
+ return nCount;
+}
+
+std::size_t SvFileStream::PutData( const void* pData, std::size_t nSize )
+{
+ DWORD nCount = 0;
+ if( IsOpen() )
+ {
+ if(!WriteFile(mxFileHandle,pData,nSize,&nCount,nullptr))
+ SetError(::GetSvError( GetLastError() ) );
+ }
+ return nCount;
+}
+
+sal_uInt64 SvFileStream::SeekPos(sal_uInt64 const nPos)
+{
+ // check if a truncated STREAM_SEEK_TO_END was passed
+ assert(nPos != SAL_MAX_UINT32);
+ LARGE_INTEGER nNewPos, nActPos;
+ nNewPos.QuadPart = 0;
+ nActPos.QuadPart = nPos;
+ bool result = false;
+ if( IsOpen() )
+ {
+ if( nPos != STREAM_SEEK_TO_END )
+ {
+ result = SetFilePointerEx(mxFileHandle, nActPos, &nNewPos, FILE_BEGIN);
+ }
+ else
+ {
+ result = SetFilePointerEx(mxFileHandle, nNewPos, &nNewPos, FILE_END);
+ }
+ if (!result)
+ {
+ SetError(::GetSvError(GetLastError()));
+ return 0;
+ }
+ }
+ else
+ SetError( SVSTREAM_GENERALERROR );
+ return static_cast<sal_uInt64>(nNewPos.QuadPart);
+}
+
+void SvFileStream::FlushData()
+{
+ if( IsOpen() )
+ {
+ if( !FlushFileBuffers(mxFileHandle) )
+ SetError(::GetSvError(GetLastError()));
+ }
+}
+
+bool SvFileStream::LockFile()
+{
+ bool bRetVal = false;
+ if( !nLockCounter )
+ {
+ if( IsOpen() )
+ {
+ bRetVal = ::LockFile(mxFileHandle,0L,0L,LONG_MAX,0L );
+ if( bRetVal )
+ {
+ nLockCounter = 1;
+ }
+ else
+ SetError(::GetSvError(GetLastError()));
+ }
+ }
+ else
+ {
+ nLockCounter++;
+ bRetVal = true;
+ }
+ return bRetVal;
+}
+
+void SvFileStream::UnlockFile()
+{
+ if( nLockCounter > 0)
+ {
+ if( nLockCounter == 1)
+ {
+ if( IsOpen() )
+ {
+ if( ::UnlockFile(mxFileHandle,0L,0L,LONG_MAX,0L ) )
+ {
+ nLockCounter = 0;
+ }
+ else
+ SetError(::GetSvError(GetLastError()));
+ }
+ }
+ else
+ {
+ nLockCounter--;
+ }
+ }
+}
+
+/*
+ NOCREATE TRUNC NT-Action
+ ----------------------------------------------
+ 0 (Create) 0 OPEN_ALWAYS
+ 0 (Create) 1 CREATE_ALWAYS
+ 1 0 OPEN_EXISTING
+ 1 1 TRUNCATE_EXISTING
+*/
+void SvFileStream::Open( const OUString& rFilename, StreamMode nMode )
+{
+ OUString aParsedFilename(rFilename);
+
+ SetLastError( ERROR_SUCCESS );
+ Close();
+ SvStream::ClearBuffer();
+
+ m_eStreamMode = nMode;
+ m_eStreamMode &= ~StreamMode::TRUNC; // don't truncate on reopen
+
+ aFilename = aParsedFilename;
+ SetLastError( ERROR_SUCCESS ); // might be changed by Redirector
+
+ DWORD nOpenAction;
+ DWORD nShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ DWORD nAccessMode = 0;
+ UINT nOldErrorMode = SetErrorMode( SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX );
+
+ if( nMode & StreamMode::SHARE_DENYREAD)
+ nShareMode &= ~FILE_SHARE_READ;
+
+ if( nMode & StreamMode::SHARE_DENYWRITE)
+ nShareMode &= ~FILE_SHARE_WRITE;
+
+ if( nMode & StreamMode::SHARE_DENYALL)
+ nShareMode = 0;
+
+ if( nMode & StreamMode::READ )
+ nAccessMode |= GENERIC_READ;
+ if( nMode & StreamMode::WRITE )
+ nAccessMode |= GENERIC_WRITE;
+
+ if( nAccessMode == GENERIC_READ ) // ReadOnly ?
+ nMode |= StreamMode::NOCREATE; // Don't create if readonly
+
+ // Assignment based on true/false table above
+ if( !(nMode & StreamMode::NOCREATE) )
+ {
+ if( nMode & StreamMode::TRUNC )
+ nOpenAction = CREATE_ALWAYS;
+ else
+ nOpenAction = OPEN_ALWAYS;
+ }
+ else
+ {
+ if( nMode & StreamMode::TRUNC )
+ nOpenAction = TRUNCATE_EXISTING;
+ else
+ nOpenAction = OPEN_EXISTING;
+ }
+
+ DWORD nAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS;
+
+ if ( nMode & StreamMode::TEMPORARY )
+ nAttributes |= FILE_ATTRIBUTE_TEMPORARY;
+ if ( nMode & StreamMode::DELETE_ON_CLOSE )
+ nAttributes |= FILE_FLAG_DELETE_ON_CLOSE;
+
+ mxFileHandle = CreateFileW(
+ o3tl::toW(aFilename.getStr()),
+ nAccessMode,
+ nShareMode,
+ nullptr,
+ nOpenAction,
+ nAttributes,
+ nullptr
+ );
+
+ if( mxFileHandle!=INVALID_HANDLE_VALUE && (
+ // Did Create Always overwrite a file?
+ GetLastError() == ERROR_ALREADY_EXISTS ||
+ // Did Create Always open a new file?
+ GetLastError() == ERROR_FILE_NOT_FOUND ))
+ {
+ // If so, no error
+ if( nOpenAction == OPEN_ALWAYS || nOpenAction == CREATE_ALWAYS )
+ SetLastError( ERROR_SUCCESS );
+ }
+
+ // Otherwise, determine if we're allowed to read
+ if( (mxFileHandle==INVALID_HANDLE_VALUE) &&
+ (nAccessMode & GENERIC_WRITE))
+ {
+ ErrCode nErr = ::GetSvError( GetLastError() );
+ if(nErr==SVSTREAM_ACCESS_DENIED || nErr==SVSTREAM_SHARING_VIOLATION)
+ {
+ nMode &= ~StreamMode::WRITE;
+ nAccessMode = GENERIC_READ;
+ // OV, 28.1.97: Win32 sets file to length 0
+ // if Openaction is CREATE_ALWAYS
+ nOpenAction = OPEN_EXISTING;
+ SetLastError( ERROR_SUCCESS );
+ mxFileHandle = CreateFileW(
+ o3tl::toW(aFilename.getStr()),
+ GENERIC_READ,
+ nShareMode,
+ nullptr,
+ nOpenAction,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
+ nullptr
+ );
+ if( GetLastError() == ERROR_ALREADY_EXISTS )
+ SetLastError( ERROR_SUCCESS );
+ }
+ }
+
+ if( GetLastError() != ERROR_SUCCESS )
+ {
+ bIsOpen = false;
+ SetError(::GetSvError( GetLastError() ) );
+ }
+ else
+ {
+ bIsOpen = true;
+ // pInstanceData->bIsEof = false;
+ if( nAccessMode & GENERIC_WRITE )
+ m_isWritable = true;
+ }
+ SetErrorMode( nOldErrorMode );
+}
+
+void SvFileStream::Close()
+{
+ if( IsOpen() )
+ {
+ if( nLockCounter )
+ {
+ nLockCounter = 1;
+ UnlockFile();
+ }
+ FlushBuffer();
+ CloseHandle( mxFileHandle );
+ }
+ bIsOpen = false;
+ nLockCounter= 0;
+ m_isWritable = false;
+ SvStream::ClearBuffer();
+ SvStream::ClearError();
+}
+
+/// Reset filepointer to beginning of file
+void SvFileStream::ResetError()
+{
+ SvStream::ClearError();
+}
+
+void SvFileStream::SetSize(sal_uInt64 const nSize)
+{
+
+ if( IsOpen() )
+ {
+ bool bError = false;
+ HANDLE hFile = mxFileHandle;
+ DWORD const nOld = SetFilePointer( hFile, 0L, nullptr, FILE_CURRENT );
+ if( nOld != 0xffffffff )
+ {
+ if( SetFilePointer(hFile,nSize,nullptr,FILE_BEGIN ) != 0xffffffff)
+ {
+ bool bSucc = SetEndOfFile( hFile );
+ if( !bSucc )
+ bError = true;
+ }
+ if( SetFilePointer( hFile,nOld,nullptr,FILE_BEGIN ) == 0xffffffff)
+ bError = true;
+ }
+ if( bError )
+ SetError(::GetSvError( GetLastError() ) );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/stream/vcompat.cxx b/tools/source/stream/vcompat.cxx
new file mode 100644
index 0000000000..bf85c0c9a4
--- /dev/null
+++ b/tools/source/stream/vcompat.cxx
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/stream.hxx>
+#include <tools/vcompat.hxx>
+
+VersionCompatRead::VersionCompatRead(SvStream& rStm)
+ : mrRStm(rStm)
+ , mnCompatPos(0)
+ , mnTotalSize(0)
+ , mnVersion(1)
+{
+ if (mrRStm.GetError())
+ return;
+
+ mrRStm.ReadUInt16(mnVersion);
+ mrRStm.ReadUInt32(mnTotalSize);
+ mnCompatPos = mrRStm.Tell();
+}
+
+VersionCompatWrite::VersionCompatWrite(SvStream& rStm, sal_uInt16 nVersion)
+ : mrWStm(rStm)
+ , mnCompatPos(0)
+ , mnTotalSize(0)
+{
+ if (mrWStm.GetError())
+ return;
+
+ mrWStm.WriteUInt16(nVersion);
+ mnCompatPos = mrWStm.Tell();
+ mnTotalSize = mnCompatPos + 4;
+ mrWStm.SeekRel(4);
+}
+
+VersionCompatRead::~VersionCompatRead()
+{
+ const sal_uInt32 nReadSize = mrRStm.Tell() - mnCompatPos;
+
+ if (mnTotalSize > nReadSize)
+ mrRStm.SeekRel(mnTotalSize - nReadSize);
+}
+
+VersionCompatWrite::~VersionCompatWrite()
+{
+ const sal_uInt32 nEndPos = mrWStm.Tell();
+
+ mrWStm.Seek(mnCompatPos);
+ mrWStm.WriteUInt32(nEndPos - mnTotalSize);
+ mrWStm.Seek(nEndPos);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/string/tenccvt.cxx b/tools/source/string/tenccvt.cxx
new file mode 100644
index 0000000000..152200a353
--- /dev/null
+++ b/tools/source/string/tenccvt.cxx
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <rtl/tencinfo.h>
+#include <tools/tenccvt.hxx>
+
+rtl_TextEncoding GetExtendedCompatibilityTextEncoding( rtl_TextEncoding eEncoding )
+{
+ // Latin1
+ if ( eEncoding == RTL_TEXTENCODING_ISO_8859_1 )
+ return RTL_TEXTENCODING_MS_1252;
+ // Turkey
+ else if ( eEncoding == RTL_TEXTENCODING_ISO_8859_9 )
+ return RTL_TEXTENCODING_MS_1254;
+ else
+ return eEncoding;
+}
+
+rtl_TextEncoding GetExtendedTextEncoding( rtl_TextEncoding eEncoding )
+{
+ // Cyr
+ if ( eEncoding == RTL_TEXTENCODING_ISO_8859_5 )
+ return RTL_TEXTENCODING_MS_1251;
+ // Greek (2 Characters different: A1 (0x2018/0x0385), A2 (0x2019/0x0386) -
+ // so it is handled in this function and not in GetExtendedCompatibilityTextEncoding)
+ else if ( eEncoding == RTL_TEXTENCODING_ISO_8859_7 )
+ return RTL_TEXTENCODING_MS_1253;
+ // East-Europe - Latin2
+ else if ( eEncoding == RTL_TEXTENCODING_ISO_8859_2 )
+ return RTL_TEXTENCODING_MS_1250;
+ // Latin-15 - Latin 1 with euro sign
+ else if ( eEncoding == RTL_TEXTENCODING_ISO_8859_15 )
+ return RTL_TEXTENCODING_MS_1252;
+ else
+ return GetExtendedCompatibilityTextEncoding( eEncoding );
+}
+
+rtl_TextEncoding GetOneByteTextEncoding( rtl_TextEncoding eEncoding )
+{
+ rtl_TextEncodingInfo aTextEncInfo;
+ aTextEncInfo.StructSize = sizeof( aTextEncInfo );
+ if ( rtl_getTextEncodingInfo( eEncoding, &aTextEncInfo ) )
+ {
+ if ( aTextEncInfo.MaximumCharSize > 1 )
+ return RTL_TEXTENCODING_MS_1252;
+ else
+ return eEncoding;
+ }
+ else
+ return RTL_TEXTENCODING_MS_1252;
+}
+
+rtl_TextEncoding GetSOLoadTextEncoding( rtl_TextEncoding eEncoding )
+{
+ return GetExtendedCompatibilityTextEncoding( GetOneByteTextEncoding( eEncoding ) );
+}
+
+rtl_TextEncoding GetSOStoreTextEncoding( rtl_TextEncoding eEncoding )
+{
+ return GetExtendedTextEncoding( GetOneByteTextEncoding( eEncoding ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/xml/XmlWalker.cxx b/tools/source/xml/XmlWalker.cxx
new file mode 100644
index 0000000000..c0e8a2abef
--- /dev/null
+++ b/tools/source/xml/XmlWalker.cxx
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <tools/stream.hxx>
+#include <tools/XmlWalker.hxx>
+
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xmlstring.h>
+#include <vector>
+
+namespace tools
+{
+struct XmlWalkerImpl
+{
+ XmlWalkerImpl()
+ : mpDocPtr(nullptr)
+ , mpRoot(nullptr)
+ , mpCurrent(nullptr)
+ {
+ }
+
+ xmlDocPtr mpDocPtr;
+ xmlNodePtr mpRoot;
+ xmlNodePtr mpCurrent;
+
+ std::vector<xmlNodePtr> mpStack;
+};
+
+XmlWalker::XmlWalker()
+ : mpImpl(std::make_unique<XmlWalkerImpl>())
+{
+}
+
+XmlWalker::~XmlWalker()
+{
+ if (mpImpl)
+ xmlFreeDoc(mpImpl->mpDocPtr);
+}
+
+bool XmlWalker::open(SvStream* pStream)
+{
+ std::size_t nSize = pStream->remainingSize();
+ std::vector<sal_uInt8> aBuffer(nSize + 1);
+ pStream->ReadBytes(aBuffer.data(), nSize);
+ aBuffer[nSize] = 0;
+ mpImpl->mpDocPtr = xmlParseDoc(reinterpret_cast<xmlChar*>(aBuffer.data()));
+ if (mpImpl->mpDocPtr == nullptr)
+ return false;
+ mpImpl->mpRoot = xmlDocGetRootElement(mpImpl->mpDocPtr);
+ mpImpl->mpCurrent = mpImpl->mpRoot;
+ mpImpl->mpStack.push_back(mpImpl->mpCurrent);
+ return true;
+}
+
+OString XmlWalker::name() { return reinterpret_cast<const char*>(mpImpl->mpCurrent->name); }
+
+OString XmlWalker::namespaceHref()
+{
+ return reinterpret_cast<const char*>(mpImpl->mpCurrent->ns->href);
+}
+
+OString XmlWalker::namespacePrefix()
+{
+ return reinterpret_cast<const char*>(mpImpl->mpCurrent->ns->prefix);
+}
+
+OString XmlWalker::content()
+{
+ OString aContent;
+ if (mpImpl->mpCurrent->xmlChildrenNode != nullptr)
+ {
+ xmlChar* pContent
+ = xmlNodeListGetString(mpImpl->mpDocPtr, mpImpl->mpCurrent->xmlChildrenNode, 1);
+ aContent = OString(reinterpret_cast<const char*>(pContent));
+ xmlFree(pContent);
+ }
+ return aContent;
+}
+
+void XmlWalker::children()
+{
+ mpImpl->mpStack.push_back(mpImpl->mpCurrent);
+ mpImpl->mpCurrent = mpImpl->mpCurrent->xmlChildrenNode;
+}
+
+void XmlWalker::parent()
+{
+ mpImpl->mpCurrent = mpImpl->mpStack.back();
+ mpImpl->mpStack.pop_back();
+}
+
+OString XmlWalker::attribute(const OString& sName) const
+{
+ xmlChar* xmlAttribute
+ = xmlGetProp(mpImpl->mpCurrent, reinterpret_cast<const xmlChar*>(sName.getStr()));
+ OString aAttributeContent(
+ xmlAttribute == nullptr ? "" : reinterpret_cast<const char*>(xmlAttribute));
+ xmlFree(xmlAttribute);
+
+ return aAttributeContent;
+}
+
+void XmlWalker::next() { mpImpl->mpCurrent = mpImpl->mpCurrent->next; }
+
+bool XmlWalker::isValid() const { return mpImpl->mpCurrent != nullptr; }
+
+} // end tools namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/xml/XmlWriter.cxx b/tools/source/xml/XmlWriter.cxx
new file mode 100644
index 0000000000..424b13c67b
--- /dev/null
+++ b/tools/source/xml/XmlWriter.cxx
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <tools/stream.hxx>
+#include <tools/XmlWriter.hxx>
+
+#include <libxml/xmlwriter.h>
+
+namespace tools
+{
+namespace
+{
+int funcWriteCallback(void* pContext, const char* sBuffer, int nLen)
+{
+ SvStream* pStream = static_cast<SvStream*>(pContext);
+ return static_cast<int>(pStream->WriteBytes(sBuffer, nLen));
+}
+
+int funcCloseCallback(void* pContext)
+{
+ SvStream* pStream = static_cast<SvStream*>(pContext);
+ pStream->Flush();
+ return 0; // 0 or -1 in case of error
+}
+
+} // end anonymous namespace
+
+struct XmlWriterImpl
+{
+ XmlWriterImpl(SvStream* pStream)
+ : mpStream(pStream)
+ , mpWriter(nullptr)
+ , mbWriteXmlHeader(true)
+ {
+ }
+
+ SvStream* mpStream;
+ xmlTextWriterPtr mpWriter;
+ bool mbWriteXmlHeader;
+};
+
+XmlWriter::XmlWriter(SvStream* pStream)
+ : mpImpl(std::make_unique<XmlWriterImpl>(pStream))
+{
+}
+
+XmlWriter::~XmlWriter()
+{
+ if (mpImpl && mpImpl->mpWriter != nullptr)
+ endDocument();
+}
+
+bool XmlWriter::startDocument(sal_Int32 nIndent, bool bWriteXmlHeader)
+{
+ mpImpl->mbWriteXmlHeader = bWriteXmlHeader;
+ xmlCharEncodingHandlerPtr pEncodingHandler = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
+ xmlOutputBufferPtr xmlOutBuffer = xmlOutputBufferCreateIO(funcWriteCallback, funcCloseCallback,
+ mpImpl->mpStream, pEncodingHandler);
+ mpImpl->mpWriter = xmlNewTextWriter(xmlOutBuffer);
+ if (mpImpl->mpWriter == nullptr)
+ return false;
+ xmlTextWriterSetIndent(mpImpl->mpWriter, nIndent);
+ if (mpImpl->mbWriteXmlHeader)
+ (void)xmlTextWriterStartDocument(mpImpl->mpWriter, nullptr, "UTF-8", nullptr);
+ return true;
+}
+
+void XmlWriter::endDocument()
+{
+ if (mpImpl->mbWriteXmlHeader)
+ (void)xmlTextWriterEndDocument(mpImpl->mpWriter);
+ xmlFreeTextWriter(mpImpl->mpWriter);
+ mpImpl->mpWriter = nullptr;
+}
+
+void XmlWriter::startElement(const OString& sPrefix, const OString& sName,
+ const OString& sNamespaceUri)
+{
+ xmlChar* xmlName = BAD_CAST(sName.getStr());
+ xmlChar* xmlPrefix = nullptr;
+ xmlChar* xmlNamespaceUri = nullptr;
+ if (!sPrefix.isEmpty())
+ xmlPrefix = BAD_CAST(sPrefix.getStr());
+ if (!sNamespaceUri.isEmpty())
+ xmlNamespaceUri = BAD_CAST(sNamespaceUri.getStr());
+
+ (void)xmlTextWriterStartElementNS(mpImpl->mpWriter, xmlPrefix, xmlName, xmlNamespaceUri);
+}
+
+void XmlWriter::startElement(const char* pName)
+{
+ xmlChar* xmlName = BAD_CAST(pName);
+ (void)xmlTextWriterStartElement(mpImpl->mpWriter, xmlName);
+}
+
+void XmlWriter::startElement(const OString& rName)
+{
+ xmlChar* xmlName = BAD_CAST(rName.getStr());
+ (void)xmlTextWriterStartElement(mpImpl->mpWriter, xmlName);
+}
+
+void XmlWriter::endElement() { (void)xmlTextWriterEndElement(mpImpl->mpWriter); }
+
+void XmlWriter::attributeBase64(const char* pName, std::vector<sal_uInt8> const& rValueInBytes)
+{
+ std::vector<char> aSignedBytes(rValueInBytes.begin(), rValueInBytes.end());
+ attributeBase64(pName, aSignedBytes);
+}
+
+void XmlWriter::attributeBase64(const char* pName, std::vector<char> const& rValueInBytes)
+{
+ xmlChar* xmlName = BAD_CAST(pName);
+ (void)xmlTextWriterStartAttribute(mpImpl->mpWriter, xmlName);
+ (void)xmlTextWriterWriteBase64(mpImpl->mpWriter, rValueInBytes.data(), 0, rValueInBytes.size());
+ (void)xmlTextWriterEndAttribute(mpImpl->mpWriter);
+}
+
+void XmlWriter::attribute(const char* name, const OString& value)
+{
+ xmlChar* xmlName = BAD_CAST(name);
+ xmlChar* xmlValue = BAD_CAST(value.getStr());
+ (void)xmlTextWriterWriteAttribute(mpImpl->mpWriter, xmlName, xmlValue);
+}
+
+void XmlWriter::attribute(const OString& name, const OString& value)
+{
+ xmlChar* xmlName = BAD_CAST(name.getStr());
+ xmlChar* xmlValue = BAD_CAST(value.getStr());
+ (void)xmlTextWriterWriteAttribute(mpImpl->mpWriter, xmlName, xmlValue);
+}
+
+void XmlWriter::attribute(const char* name, std::u16string_view value)
+{
+ attribute(name, OUStringToOString(value, RTL_TEXTENCODING_UTF8));
+}
+
+void XmlWriter::attribute(const char* name, const sal_Int32 aNumber)
+{
+ attribute(name, OString::number(aNumber));
+}
+
+void XmlWriter::attributeDouble(const char* name, const double aNumber)
+{
+ attribute(name, OString::number(aNumber));
+}
+
+void XmlWriter::content(const OString& sValue)
+{
+ xmlChar* xmlValue = BAD_CAST(sValue.getStr());
+ (void)xmlTextWriterWriteString(mpImpl->mpWriter, xmlValue);
+}
+
+void XmlWriter::content(std::u16string_view sValue)
+{
+ content(OUStringToOString(sValue, RTL_TEXTENCODING_UTF8));
+}
+
+void XmlWriter::element(const char* sName)
+{
+ startElement(sName);
+ endElement();
+}
+
+} // end tools namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/tools/source/zcodec/zcodec.cxx b/tools/source/zcodec/zcodec.cxx
new file mode 100644
index 0000000000..c925ecde8a
--- /dev/null
+++ b/tools/source/zcodec/zcodec.cxx
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <algorithm>
+
+#include <tools/stream.hxx>
+
+#include <zlib.h>
+
+#include <tools/zcodec.hxx>
+#include <tools/long.hxx>
+
+/* gzip flag byte */
+// GZ_ASCII_FLAG = 0x01; /* bit 0 set: file probably ascii text */
+constexpr sal_uInt8 GZ_HEAD_CRC = 0x02; /* bit 1 set: header CRC present */
+constexpr sal_uInt8 GZ_EXTRA_FIELD = 0x04; /* bit 2 set: extra field present */
+constexpr sal_uInt8 GZ_ORIG_NAME = 0x08; /* bit 3 set: original file name present */
+constexpr sal_uInt8 GZ_COMMENT = 0x10; /* bit 4 set: file comment present */
+constexpr sal_uInt8 GZ_RESERVED = 0xE0; /* bits 5..7: reserved */
+constexpr sal_uInt16 GZ_MAGIC_BYTES_LE = 0x8B1F; /* gzip magic bytes, little endian */
+constexpr sal_uInt8 GZ_DEFLATE = 0x08;
+constexpr sal_uInt8 GZ_FS_UNKNOWN = 0xFF;
+
+ZCodec::ZCodec( size_t nInBufSize, size_t nOutBufSize )
+ : meState(STATE_INIT)
+ , mbStatus(false)
+ , mbFinish(false)
+ , mnInBufSize(nInBufSize)
+ , mnInToRead(0)
+ , mpOStm(nullptr)
+ , mnOutBufSize(nOutBufSize)
+ , mnUncompressedSize(0)
+ , mnInBufCRC32(0)
+ , mnLastModifiedTime(0)
+ , mnCompressLevel(0)
+ , mbGzLib(false)
+{
+ mpsC_Stream = new z_stream;
+}
+
+ZCodec::~ZCodec()
+{
+ auto pStream = static_cast<z_stream*>(mpsC_Stream);
+ delete pStream;
+}
+
+bool ZCodec::IsZCompressed( SvStream& rIStm )
+{
+ sal_uInt64 nCurPos = rIStm.Tell();
+ rIStm.Seek( 0 );
+ sal_uInt16 nFirstTwoBytes = 0;
+ rIStm.ReadUInt16( nFirstTwoBytes );
+ rIStm.Seek( nCurPos );
+ return nFirstTwoBytes == GZ_MAGIC_BYTES_LE;
+}
+
+void ZCodec::BeginCompression( int nCompressLevel, bool gzLib )
+{
+ assert(meState == STATE_INIT);
+ mbStatus = true;
+ mbFinish = false;
+ mpOStm = nullptr;
+ mnInToRead = 0xffffffff;
+ mpInBuf.reset();
+ mpOutBuf.reset();
+ auto pStream = static_cast<z_stream*>(mpsC_Stream);
+ pStream->total_out = pStream->total_in = 0;
+ mnCompressLevel = nCompressLevel;
+ mbGzLib = gzLib;
+ pStream->zalloc = nullptr;
+ pStream->zfree = nullptr;
+ pStream->opaque = nullptr;
+ pStream->avail_out = pStream->avail_in = 0;
+}
+
+tools::Long ZCodec::EndCompression()
+{
+ tools::Long retvalue = 0;
+ auto pStream = static_cast<z_stream*>(mpsC_Stream);
+
+ if (meState != STATE_INIT)
+ {
+ if (meState == STATE_COMPRESS)
+ {
+ if (mbStatus)
+ {
+ do
+ {
+ ImplWriteBack();
+ }
+ while ( deflate( pStream, Z_FINISH ) != Z_STREAM_END );
+
+ ImplWriteBack();
+ }
+
+ retvalue = pStream->total_in;
+ deflateEnd( pStream );
+ if ( mbGzLib )
+ {
+ // metadata must be set to compress as gz format
+ assert(!msFilename.isEmpty());
+ // overwrite zlib checksum
+ mpOStm->Seek(STREAM_SEEK_TO_END);
+ mpOStm->SeekRel(-4);
+ mpOStm->WriteUInt32( mnInBufCRC32 ); // Uncompressed buffer CRC32
+ mpOStm->WriteUInt32( mnUncompressedSize ); // Uncompressed size mod 2^32
+ mpOStm->Seek( 0 );
+ mpOStm->WriteUInt16( GZ_MAGIC_BYTES_LE ) // Magic bytes
+ .WriteUInt8( GZ_DEFLATE ) // Compression algorithm
+ .WriteUInt8( GZ_ORIG_NAME ) // Filename
+ .WriteUInt32( mnLastModifiedTime ) // Modification time
+ .WriteUInt8( 0 ) // Extra flags
+ .WriteUInt8( GZ_FS_UNKNOWN ) // Operating system
+ .WriteBytes( msFilename.pData->buffer, msFilename.pData->length );
+ mpOStm->WriteUInt8( 0 ); // null terminate the filename string
+ }
+ }
+ else
+ {
+ retvalue = pStream->total_out;
+ inflateEnd( pStream );
+ }
+ mpOutBuf.reset();
+ mpInBuf.reset();
+ meState = STATE_INIT;
+ }
+ return mbStatus ? retvalue : -1;
+}
+
+void ZCodec::SetCompressionMetadata( const OString& sFilename, sal_uInt32 nLastModifiedTime, sal_uInt32 nInBufCRC32 )
+{
+ assert( mbGzLib );
+ msFilename = sFilename;
+ mnLastModifiedTime = nLastModifiedTime;
+ mnInBufCRC32 = nInBufCRC32;
+}
+
+void ZCodec::Compress( SvStream& rIStm, SvStream& rOStm )
+{
+ assert(meState == STATE_INIT);
+ mpOStm = &rOStm;
+ rIStm.Seek(0);
+ mnUncompressedSize = rIStm.TellEnd();
+ InitCompress();
+ mpInBuf.reset(new sal_uInt8[ mnInBufSize ]);
+ auto pStream = static_cast<z_stream*>(mpsC_Stream);
+ for (;;)
+ {
+ pStream->next_in = mpInBuf.get();
+ pStream->avail_in = rIStm.ReadBytes( mpInBuf.get(), mnInBufSize );
+ if (pStream->avail_in == 0)
+ break;
+ if ( pStream->avail_out == 0 )
+ ImplWriteBack();
+ if ( deflate( pStream, Z_NO_FLUSH ) < 0 )
+ {
+ mbStatus = false;
+ break;
+ }
+ };
+}
+
+tools::Long ZCodec::Decompress( SvStream& rIStm, SvStream& rOStm )
+{
+ int err;
+ size_t nInToRead;
+ auto pStream = static_cast<z_stream*>(mpsC_Stream);
+ tools::Long nOldTotal_Out = pStream->total_out;
+
+ assert(meState == STATE_INIT);
+ mpOStm = &rOStm;
+ InitDecompress(rIStm);
+ pStream->avail_out = mnOutBufSize;
+ mpOutBuf.reset(new sal_uInt8[ pStream->avail_out ]);
+ pStream->next_out = mpOutBuf.get();
+ do
+ {
+ if ( pStream->avail_out == 0 ) ImplWriteBack();
+ if ( pStream->avail_in == 0 && mnInToRead )
+ {
+ nInToRead = std::min( mnInBufSize, mnInToRead );
+ pStream->next_in = mpInBuf.get();
+ pStream->avail_in = rIStm.ReadBytes(mpInBuf.get(), nInToRead);
+ mnInToRead -= nInToRead;
+ }
+ err = mbStatus ? inflate(pStream, Z_NO_FLUSH) : Z_ERRNO;
+ if (err < 0 || err == Z_NEED_DICT)
+ {
+ mbStatus = false;
+ break;
+ }
+
+ }
+ while ( ( err != Z_STREAM_END) && ( pStream->avail_in || mnInToRead ) );
+ ImplWriteBack();
+
+ return mbStatus ? static_cast<tools::Long>(pStream->total_out - nOldTotal_Out) : -1;
+}
+
+void ZCodec::Write( SvStream& rOStm, const sal_uInt8* pData, sal_uInt32 nSize )
+{
+ if (meState == STATE_INIT)
+ {
+ mpOStm = &rOStm;
+ InitCompress();
+ }
+ assert(&rOStm == mpOStm);
+
+ auto pStream = static_cast<z_stream*>(mpsC_Stream);
+ pStream->avail_in = nSize;
+ pStream->next_in = pData;
+
+ while ( pStream->avail_in || ( pStream->avail_out == 0 ) )
+ {
+ if ( pStream->avail_out == 0 )
+ ImplWriteBack();
+
+ if ( deflate( pStream, Z_NO_FLUSH ) < 0 )
+ {
+ mbStatus = false;
+ break;
+ }
+ }
+}
+
+tools::Long ZCodec::Read( SvStream& rIStm, sal_uInt8* pData, sal_uInt32 nSize )
+{
+ int err;
+ size_t nInToRead;
+
+ if ( mbFinish )
+ return 0; // pStream->total_out;
+
+ if (meState == STATE_INIT)
+ {
+ InitDecompress(rIStm);
+ }
+ auto pStream = static_cast<z_stream*>(mpsC_Stream);
+ pStream->avail_out = nSize;
+ pStream->next_out = pData;
+ do
+ {
+ if ( pStream->avail_in == 0 && mnInToRead )
+ {
+ nInToRead = std::min(mnInBufSize, mnInToRead);
+ pStream->next_in = mpInBuf.get();
+ pStream->avail_in = rIStm.ReadBytes(mpInBuf.get(), nInToRead);
+ mnInToRead -= nInToRead;
+ }
+ err = mbStatus ? inflate(pStream, Z_NO_FLUSH) : Z_ERRNO;
+ if (err < 0 || err == Z_NEED_DICT)
+ {
+ // Accept Z_BUF_ERROR as EAGAIN or EWOULDBLOCK.
+ mbStatus = (err == Z_BUF_ERROR);
+ break;
+ }
+ }
+ while ( (err != Z_STREAM_END) &&
+ (pStream->avail_out != 0) &&
+ (pStream->avail_in || mnInToRead) );
+ if ( err == Z_STREAM_END )
+ mbFinish = true;
+
+ return (mbStatus ? static_cast<tools::Long>(nSize - pStream->avail_out) : -1);
+}
+
+void ZCodec::ImplWriteBack()
+{
+ auto pStream = static_cast<z_stream*>(mpsC_Stream);
+ size_t nAvail = mnOutBufSize - pStream->avail_out;
+
+ if ( nAvail > 0 )
+ {
+ pStream->next_out = mpOutBuf.get();
+ mpOStm->WriteBytes( mpOutBuf.get(), nAvail );
+ pStream->avail_out = mnOutBufSize;
+ }
+}
+
+void ZCodec::InitCompress()
+{
+ assert(meState == STATE_INIT);
+ if (mbGzLib)
+ {
+ // Seek just enough so that the zlib header is overwritten after compression
+ // with the gz header
+ // 10 header bytes + filename length + null terminator - 2 bytes for
+ // zlib header that gets overwritten
+ mpOStm->Seek(10 + msFilename.getLength() + 1 - 2);
+ }
+ meState = STATE_COMPRESS;
+ auto pStream = static_cast<z_stream*>(mpsC_Stream);
+ mbStatus = deflateInit2_(
+ pStream, mnCompressLevel, Z_DEFLATED, MAX_WBITS, MAX_MEM_LEVEL,
+ Z_DEFAULT_STRATEGY, ZLIB_VERSION, sizeof (z_stream)) >= 0;
+ mpOutBuf.reset(new sal_uInt8[mnOutBufSize]);
+ pStream->next_out = mpOutBuf.get();
+ pStream->avail_out = mnOutBufSize;
+}
+
+void ZCodec::InitDecompress(SvStream & inStream)
+{
+ assert(meState == STATE_INIT);
+ auto pStream = static_cast<z_stream*>(mpsC_Stream);
+ if ( mbStatus && mbGzLib )
+ {
+ sal_uInt8 j, nMethod, nFlags;
+ sal_uInt16 nFirstTwoBytes;
+ inStream.Seek( 0 );
+ inStream.ReadUInt16( nFirstTwoBytes );
+ if ( nFirstTwoBytes != GZ_MAGIC_BYTES_LE )
+ mbStatus = false;
+ inStream.ReadUChar( nMethod );
+ inStream.ReadUChar( nFlags );
+ if ( nMethod != Z_DEFLATED )
+ mbStatus = false;
+ if ( ( nFlags & GZ_RESERVED ) != 0 )
+ mbStatus = false;
+ /* Discard time, xflags and OS code: */
+ inStream.SeekRel( 6 );
+ /* skip the extra field */
+ if ( nFlags & GZ_EXTRA_FIELD )
+ {
+ sal_uInt8 n1, n2;
+ inStream.ReadUChar( n1 ).ReadUChar( n2 );
+ inStream.SeekRel( n1 + ( n2 << 8 ) );
+ }
+ /* skip the original file name */
+ if ( nFlags & GZ_ORIG_NAME)
+ {
+ do
+ {
+ inStream.ReadUChar( j );
+ }
+ while ( j && !inStream.eof() );
+ }
+ /* skip the .gz file comment */
+ if ( nFlags & GZ_COMMENT )
+ {
+ do
+ {
+ inStream.ReadUChar( j );
+ }
+ while ( j && !inStream.eof() );
+ }
+ /* skip the header crc */
+ if ( nFlags & GZ_HEAD_CRC )
+ inStream.SeekRel( 2 );
+ if ( mbStatus )
+ mbStatus = inflateInit2( pStream, -MAX_WBITS) == Z_OK;
+ }
+ else
+ {
+ mbStatus = ( inflateInit( pStream ) >= 0 );
+ }
+ if ( mbStatus )
+ meState = STATE_DECOMPRESS;
+ mpInBuf.reset(new sal_uInt8[ mnInBufSize ]);
+}
+
+bool ZCodec::AttemptDecompression(SvStream& rIStm, SvStream& rOStm)
+{
+ assert(meState == STATE_INIT);
+ sal_uInt64 nStreamPos = rIStm.Tell();
+ BeginCompression(ZCODEC_DEFAULT_COMPRESSION, true/*gzLib*/);
+ InitDecompress(rIStm);
+ EndCompression();
+ if ( !mbStatus || rIStm.GetError() )
+ {
+ rIStm.Seek(nStreamPos);
+ return false;
+ }
+ rIStm.Seek(nStreamPos);
+ BeginCompression(ZCODEC_DEFAULT_COMPRESSION, true/*gzLib*/);
+ Decompress(rIStm, rOStm);
+ EndCompression();
+ if( !mbStatus || rIStm.GetError() || rOStm.GetError() )
+ {
+ rIStm.Seek(nStreamPos);
+ return false;
+ }
+ rIStm.Seek(nStreamPos);
+ rOStm.Seek(0);
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */