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