10617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen/*
265f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * Copyright (C) Research In Motion Limited 2010. All rights reserved.
365f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch *
465f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * This library is free software; you can redistribute it and/or
565f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * modify it under the terms of the GNU Library General Public
665f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * License as published by the Free Software Foundation; either
765f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * version 2 of the License, or (at your option) any later version.
865f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch *
965f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * This library is distributed in the hope that it will be useful,
1065f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * but WITHOUT ANY WARRANTY; without even the implied warranty of
1165f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
1265f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * Library General Public License for more details.
1365f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch *
1465f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * You should have received a copy of the GNU Library General Public License
1565f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * along with this library; see the file COPYING.LIB.  If not, write to
1665f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
1765f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch * Boston, MA 02110-1301, USA.
1865f03d4f644ce73618e5f4f50dd694b26f55ae12Ben Murdoch */
190617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
200617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#include "config.h"
210617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#include "SVGResourcesCycleSolver.h"
220617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
230617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen// Set to a value > 0, to debug the resource cache.
240617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#define DEBUG_CYCLE_DETECTION 0
250617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
260617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#if ENABLE(SVG)
270617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#include "RenderSVGResourceClipper.h"
280617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#include "RenderSVGResourceFilter.h"
290617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#include "RenderSVGResourceMarker.h"
300617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#include "RenderSVGResourceMasker.h"
310617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#include "SVGFilterElement.h"
320617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#include "SVGGradientElement.h"
330617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#include "SVGPatternElement.h"
340617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#include "SVGResources.h"
350617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#include "SVGResourcesCache.h"
360617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
370617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsennamespace WebCore {
380617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
390617145a89917ae7735fe1c9538688ab9a577df5Kristian MonsenSVGResourcesCycleSolver::SVGResourcesCycleSolver(RenderObject* renderer, SVGResources* resources)
400617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    : m_renderer(renderer)
410617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    , m_resources(resources)
420617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen{
430617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    ASSERT(m_renderer);
440617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    ASSERT(m_resources);
450617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen}
460617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
470617145a89917ae7735fe1c9538688ab9a577df5Kristian MonsenSVGResourcesCycleSolver::~SVGResourcesCycleSolver()
480617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen{
490617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen}
500617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
510617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsenbool SVGResourcesCycleSolver::resourceContainsCycles(RenderObject* renderer) const
520617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen{
530617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    ASSERT(renderer);
540617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
550617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    // First operate on the resources of the given renderer.
560617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    // <marker id="a"> <path marker-start="url(#b)"/> ...
570617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    // <marker id="b" marker-start="url(#a)"/>
580617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    if (SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(renderer)) {
590617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        HashSet<RenderSVGResourceContainer*> resourceSet;
600617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        resources->buildSetOfResources(resourceSet);
610617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
620617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        // Walk all resources and check wheter they reference any resource contained in the resources set.
630617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        HashSet<RenderSVGResourceContainer*>::iterator end = resourceSet.end();
640617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        for (HashSet<RenderSVGResourceContainer*>::iterator it = resourceSet.begin(); it != end; ++it) {
650617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            if (m_allResources.contains(*it))
660617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen                return true;
670617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        }
680617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    }
690617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
700617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    // Then operate on the child resources of the given renderer.
710617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    // <marker id="a"> <path marker-start="url(#b)"/> ...
720617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    // <marker id="b"> <path marker-start="url(#a)"/> ...
730617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    for (RenderObject* child = renderer->firstChild(); child; child = child->nextSibling()) {
740617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        SVGResources* childResources = SVGResourcesCache::cachedResourcesForRenderObject(child);
750617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        if (!childResources)
760617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            continue;
770617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
780617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        // A child of the given 'resource' contains resources.
790617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        HashSet<RenderSVGResourceContainer*> childSet;
800617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        childResources->buildSetOfResources(childSet);
810617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
820617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        // Walk all child resources and check wheter they reference any resource contained in the resources set.
830617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        HashSet<RenderSVGResourceContainer*>::iterator end = childSet.end();
840617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        for (HashSet<RenderSVGResourceContainer*>::iterator it = childSet.begin(); it != end; ++it) {
850617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            if (m_allResources.contains(*it))
860617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen                return true;
870617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        }
880617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
890617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        // Walk children recursively, stop immediately if we found a cycle
900617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        if (resourceContainsCycles(child))
910617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            return true;
920617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    }
930617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
940617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    return false;
950617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen}
960617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
970617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsenvoid SVGResourcesCycleSolver::resolveCycles()
980617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen{
990617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    ASSERT(m_allResources.isEmpty());
1000617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
1010617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#if DEBUG_CYCLE_DETECTION > 0
1020617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    fprintf(stderr, "\nBefore cycle detection:\n");
1030617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    m_resources->dump(m_renderer);
1040617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#endif
1050617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
1060617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    // Stash all resources into a HashSet for the ease of traversing.
1070617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    HashSet<RenderSVGResourceContainer*> localResources;
1080617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    m_resources->buildSetOfResources(localResources);
1090617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    ASSERT(!localResources.isEmpty());
1100617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
1110617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    // Add all parent resource containers to the HashSet.
1120617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    HashSet<RenderSVGResourceContainer*> parentResources;
1130617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    RenderObject* parent = m_renderer->parent();
1140617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    while (parent) {
1150617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        if (parent->isSVGResourceContainer())
1160617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            parentResources.add(parent->toRenderSVGResourceContainer());
1170617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        parent = parent->parent();
1180617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    }
1190617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
1200617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#if DEBUG_CYCLE_DETECTION > 0
1210617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    fprintf(stderr, "\nDetecting wheter any resources references any of following objects:\n");
1220617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    {
1230617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        fprintf(stderr, "Local resources:\n");
1240617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        HashSet<RenderSVGResourceContainer*>::iterator end = localResources.end();
1250617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        for (HashSet<RenderSVGResourceContainer*>::iterator it = localResources.begin(); it != end; ++it)
1260617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            fprintf(stderr, "|> %s: object=%p (node=%p)\n", (*it)->renderName(), *it, (*it)->node());
1270617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
1280617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        fprintf(stderr, "Parent resources:\n");
1290617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        end = parentResources.end();
1300617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        for (HashSet<RenderSVGResourceContainer*>::iterator it = parentResources.begin(); it != end; ++it)
1310617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            fprintf(stderr, "|> %s: object=%p (node=%p)\n", (*it)->renderName(), *it, (*it)->node());
1320617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    }
1330617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#endif
1340617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
1350617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    // Build combined set of local and parent resources.
1360617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    m_allResources = localResources;
1370617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    HashSet<RenderSVGResourceContainer*>::iterator end = parentResources.end();
1380617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    for (HashSet<RenderSVGResourceContainer*>::iterator it = parentResources.begin(); it != end; ++it)
1390617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        m_allResources.add(*it);
1400617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
141db14019a23d96bc8a444b6576a5da8bd1cfbc8b0Steve Block    // If we're a resource, add ourselves to the HashSet.
142db14019a23d96bc8a444b6576a5da8bd1cfbc8b0Steve Block    if (m_renderer->isSVGResourceContainer())
143db14019a23d96bc8a444b6576a5da8bd1cfbc8b0Steve Block        m_allResources.add(m_renderer->toRenderSVGResourceContainer());
144db14019a23d96bc8a444b6576a5da8bd1cfbc8b0Steve Block
1450617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    ASSERT(!m_allResources.isEmpty());
1460617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
1470617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    // The job of this function is to determine wheter any of the 'resources' associated with the given 'renderer'
1480617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    // references us (or wheter any of its kids references us) -> that's a cycle, we need to find and break it.
1490617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    end = localResources.end();
1500617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    for (HashSet<RenderSVGResourceContainer*>::iterator it = localResources.begin(); it != end; ++it) {
1510617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        RenderSVGResourceContainer* resource = *it;
1520617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        if (parentResources.contains(resource) || resourceContainsCycles(resource))
1530617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            breakCycle(resource);
1540617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    }
1550617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
1560617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#if DEBUG_CYCLE_DETECTION > 0
1570617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    fprintf(stderr, "\nAfter cycle detection:\n");
1580617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    m_resources->dump(m_renderer);
1590617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#endif
1600617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
1610617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    m_allResources.clear();
1620617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen}
1630617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
1640617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsenvoid SVGResourcesCycleSolver::breakCycle(RenderSVGResourceContainer* resourceLeadingToCycle)
1650617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen{
1660617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    ASSERT(resourceLeadingToCycle);
167db14019a23d96bc8a444b6576a5da8bd1cfbc8b0Steve Block    if (resourceLeadingToCycle == m_resources->linkedResource()) {
168db14019a23d96bc8a444b6576a5da8bd1cfbc8b0Steve Block        m_resources->resetLinkedResource();
169db14019a23d96bc8a444b6576a5da8bd1cfbc8b0Steve Block        return;
170db14019a23d96bc8a444b6576a5da8bd1cfbc8b0Steve Block    }
171db14019a23d96bc8a444b6576a5da8bd1cfbc8b0Steve Block
1720617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    switch (resourceLeadingToCycle->resourceType()) {
1730617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    case MaskerResourceType:
1740617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        ASSERT(resourceLeadingToCycle == m_resources->masker());
1750617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        m_resources->resetMasker();
1760617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        break;
1770617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    case MarkerResourceType:
1780617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        ASSERT(resourceLeadingToCycle == m_resources->markerStart() || resourceLeadingToCycle == m_resources->markerMid() || resourceLeadingToCycle == m_resources->markerEnd());
1790617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        if (m_resources->markerStart() == resourceLeadingToCycle)
1800617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            m_resources->resetMarkerStart();
1810617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        if (m_resources->markerMid() == resourceLeadingToCycle)
1820617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            m_resources->resetMarkerMid();
1830617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        if (m_resources->markerEnd() == resourceLeadingToCycle)
1840617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            m_resources->resetMarkerEnd();
1850617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        break;
1860617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    case PatternResourceType:
1870617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    case LinearGradientResourceType:
1880617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    case RadialGradientResourceType:
1890617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        ASSERT(resourceLeadingToCycle == m_resources->fill() || resourceLeadingToCycle == m_resources->stroke());
1900617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        if (m_resources->fill() == resourceLeadingToCycle)
1910617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            m_resources->resetFill();
1920617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        if (m_resources->stroke() == resourceLeadingToCycle)
1930617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen            m_resources->resetStroke();
1940617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        break;
1950617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    case FilterResourceType:
196d246a992a1b834d070d06055f701df55c176266dKristian Monsen#if ENABLE(FILTERS)
19790f8c1ab32eff49545f4db89544caff1e577e3d4Steve Block        ASSERT(resourceLeadingToCycle == m_resources->filter());
1980617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        m_resources->resetFilter();
199d246a992a1b834d070d06055f701df55c176266dKristian Monsen#endif
2000617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        break;
2010617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    case ClipperResourceType:
2020617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        ASSERT(resourceLeadingToCycle == m_resources->clipper());
2030617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        m_resources->resetClipper();
2040617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        break;
2050617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    case SolidColorResourceType:
2060617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        ASSERT_NOT_REACHED();
2070617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen        break;
2080617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen    }
2090617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen}
2100617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
2110617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen}
2120617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen
2130617145a89917ae7735fe1c9538688ab9a577df5Kristian Monsen#endif
214