summaryrefslogtreecommitdiffstats
path: root/include/basegfx/range/b2dconnectedranges.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'include/basegfx/range/b2dconnectedranges.hxx')
-rw-r--r--include/basegfx/range/b2dconnectedranges.hxx240
1 files changed, 240 insertions, 0 deletions
diff --git a/include/basegfx/range/b2dconnectedranges.hxx b/include/basegfx/range/b2dconnectedranges.hxx
new file mode 100644
index 000000000..0d7a81481
--- /dev/null
+++ b/include/basegfx/range/b2dconnectedranges.hxx
@@ -0,0 +1,240 @@
+/* -*- 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 <osl/diagnose.h>
+#include <basegfx/range/b2drange.hxx>
+#include <list>
+#include <utility>
+#include <algorithm>
+
+
+namespace basegfx
+{
+ /** Calculate connected ranges from input ranges.
+
+ This template constructs a list of connected ranges from the
+ given input ranges. That is, the output will contain a set of
+ ranges, itself containing a number of input ranges, which will
+ be mutually non-intersecting.
+
+ Example:
+ <code>
+ -------------------
+ | -------|
+ | | ||
+ | --- | ||
+ | | | -------| --------
+ | | +--------- | | |
+ | --+ | | | |
+ | | | | --------
+ | ---------- |
+ -------------------
+ </code
+
+ Here, the outer rectangles represent the output
+ ranges. Contained are the input rectangles that comprise these
+ output ranges.
+
+ @tpl UserData
+ User data to be stored along with the range, to later identify
+ which range went into which connected component. Must be
+ assignable, default- and copy-constructible.
+ */
+ template< typename UserData > class B2DConnectedRanges
+ {
+ public:
+ /// Type of the basic entity (rect + user data)
+ typedef ::std::pair< B2DRange, UserData > ComponentType;
+ typedef ::std::list< ComponentType > ComponentListType;
+
+ /// List of (intersecting) components, plus overall bounds
+ struct ConnectedComponents
+ {
+ ComponentListType maComponentList;
+ B2DRange maTotalBounds;
+ };
+
+ typedef ::std::list< ConnectedComponents > ConnectedComponentsType;
+
+
+ /// Create the range calculator
+ B2DConnectedRanges() :
+ maDisjunctAggregatesList(),
+ maTotalBounds()
+ {
+ }
+
+ /** Add an additional range.
+
+ This method integrates a new range into the connected
+ ranges lists. The method has a worst-case time complexity
+ of O(n^2), with n denoting the number of already added
+ ranges (typically, for well-behaved input, it is O(n)
+ though).
+ */
+ void addRange( const B2DRange& rRange,
+ const UserData& rUserData )
+ {
+ // check whether fast path is possible: if new range is
+ // outside accumulated total range, can add it as a
+ // separate component right away.
+ const bool bNotOutsideEverything(
+ maTotalBounds.overlaps( rRange ) );
+
+ // update own global bounds range
+ maTotalBounds.expand( rRange );
+
+ // assemble anything intersecting with rRange into
+ // this new connected component
+ ConnectedComponents aNewConnectedComponent;
+
+ // as at least rRange will be a member of
+ // aNewConnectedComponent (will be added below), can
+ // preset the overall bounds here.
+ aNewConnectedComponent.maTotalBounds = rRange;
+
+
+ // STAGE 1: Search for intersecting maDisjunctAggregatesList entries
+
+
+ // if rRange is empty, it will intersect with no
+ // maDisjunctAggregatesList member. Thus, we can safe us
+ // the check.
+ // if rRange is outside all other rectangle, skip here,
+ // too
+ if( bNotOutsideEverything &&
+ !rRange.isEmpty() )
+ {
+ typename ConnectedComponentsType::iterator aCurrAggregate;
+ typename ConnectedComponentsType::iterator aLastAggregate;
+
+ // flag, determining whether we touched one or more of
+ // the maDisjunctAggregatesList entries. _If_ we did,
+ // we have to repeat the intersection process, because
+ // these changes might have generated new
+ // intersections.
+ bool bSomeAggregatesChanged;
+
+ // loop, until bSomeAggregatesChanged stays false
+ do
+ {
+ // only continue loop if 'intersects' branch below was hit
+ bSomeAggregatesChanged = false;
+
+ // iterate over all current members of maDisjunctAggregatesList
+ for( aCurrAggregate=maDisjunctAggregatesList.begin(),
+ aLastAggregate=maDisjunctAggregatesList.end();
+ aCurrAggregate != aLastAggregate; )
+ {
+ // first check if current component's bounds
+ // are empty. This ensures that distinct empty
+ // components are not merged into one
+ // aggregate. As a matter of fact, they have
+ // no position and size.
+
+ if( !aCurrAggregate->maTotalBounds.isEmpty() &&
+ aCurrAggregate->maTotalBounds.overlaps(
+ aNewConnectedComponent.maTotalBounds ) )
+ {
+ // union the intersecting
+ // maDisjunctAggregatesList element into
+ // aNewConnectedComponent
+
+ // calc union bounding box
+ aNewConnectedComponent.maTotalBounds.expand( aCurrAggregate->maTotalBounds );
+
+ // extract all aCurrAggregate components
+ // to aNewConnectedComponent
+ aNewConnectedComponent.maComponentList.splice(
+ aNewConnectedComponent.maComponentList.end(),
+ aCurrAggregate->maComponentList );
+
+ // remove and delete aCurrAggregate entry
+ // from list (we've gutted it's content
+ // above). list::erase() will update our
+ // iterator with the predecessor here.
+ aCurrAggregate = maDisjunctAggregatesList.erase( aCurrAggregate );
+
+ // at least one aggregate changed, need to rescan everything
+ bSomeAggregatesChanged = true;
+ }
+ else
+ {
+ ++aCurrAggregate;
+ }
+ }
+ }
+ while( bSomeAggregatesChanged );
+ }
+
+
+ // STAGE 2: Add newly generated connected component list element
+
+
+ // add new component to the end of the component list
+ aNewConnectedComponent.maComponentList.push_back(
+ ComponentType( rRange, rUserData ) );
+
+ // do some consistency checks (aka post conditions)
+ OSL_ENSURE( !aNewConnectedComponent.maComponentList.empty(),
+ "B2DConnectedRanges::addRange(): empty aggregate list" );
+ OSL_ENSURE( !aNewConnectedComponent.maTotalBounds.isEmpty() ||
+ aNewConnectedComponent.maComponentList.size() == 1,
+ "B2DConnectedRanges::addRange(): empty ranges must be solitary");
+
+ // add aNewConnectedComponent as a new entry to
+ // maDisjunctAggregatesList
+ maDisjunctAggregatesList.push_back( aNewConnectedComponent );
+ }
+
+ /** Apply a functor to each of the disjunct component
+ aggregates.
+
+ @param aFunctor
+ Functor to apply. Must provide an operator( const ConnectedComponents& ).
+
+ @return a copy of the functor, as applied to all aggregates.
+ */
+ template< typename UnaryFunctor > UnaryFunctor forEachAggregate( UnaryFunctor aFunctor ) const
+ {
+ return ::std::for_each( maDisjunctAggregatesList.begin(),
+ maDisjunctAggregatesList.end(),
+ aFunctor );
+ }
+
+ private:
+ B2DConnectedRanges(const B2DConnectedRanges&) = delete;
+ B2DConnectedRanges& operator=( const B2DConnectedRanges& ) = delete;
+
+ /** Current list of disjunct sets of connected components
+
+ Each entry corresponds to one of the top-level rectangles
+ in the drawing above.
+ */
+ ConnectedComponentsType maDisjunctAggregatesList;
+
+ /** Global bound rect over all added ranges.
+ */
+ B2DRange maTotalBounds;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */