CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-svgo

Node.js library and command-line application for optimizing SVG vector graphics files

Pending
Overview
Eval results
Files

utility-functions.mddocs/

Utility Functions

Advanced utility functions for security checking, reference detection, and numeric precision handling. These functions are commonly used in custom plugins and advanced SVG processing workflows.

Capabilities

Security Utilities

Functions for detecting potentially unsafe SVG content.

/**
 * Check if element contains scripts or event handlers
 * @param node - Element to check for script content
 * @returns True if element contains scripts or event handlers
 */
function hasScripts(node: XastElement): boolean;

Usage Examples:

import { hasScripts } from "svgo";

// Custom plugin that avoids processing elements with scripts
const safeOptimizationPlugin = {
  name: 'safeOptimization',
  fn: (root) => {
    return {
      element: {
        enter(node, parent) {
          // Skip optimization for elements with scripts or event handlers
          if (hasScripts(node)) {
            console.log(`Skipping ${node.name} - contains scripts`);
            return visitSkip;
          }
          
          // Example: Safe to optimize this element
          if (node.name === 'g' && Object.keys(node.attributes).length === 0) {
            // Can safely flatten empty groups without scripts
            node.children.forEach(child => {
              parent.children.push(child);
            });
            detachNodeFromParent(node, parent);
          }
        }
      }
    };
  }
};

// Example usage in element analysis
const analyzeScripts = (node) => {
  if (hasScripts(node)) {
    console.log('Found unsafe element:', {
      name: node.name,
      hasScriptTag: node.name === 'script',
      hasJavascriptLinks: node.name === 'a' && 
        Object.entries(node.attributes).some(([key, val]) => 
          key.includes('href') && val?.startsWith('javascript:')),
      hasEventHandlers: Object.keys(node.attributes).some(attr => 
        attr.startsWith('on') || attr.includes('event'))
    });
  }
};

Reference Detection

Functions for detecting and extracting SVG element references.

/**
 * Check if string contains URL references like url(#id)
 * @param body - String content to check
 * @returns True if string contains URL references
 */
function includesUrlReference(body: string): boolean;

/**
 * Extract reference IDs from attribute values
 * @param attribute - Attribute name to check
 * @param value - Attribute value to search
 * @returns Array of found reference IDs
 */
function findReferences(attribute: string, value: string): string[];

Usage Examples:

import { includesUrlReference, findReferences } from "svgo";

// Custom plugin that tracks SVG references
const referenceTrackerPlugin = {
  name: 'referenceTracker',
  fn: (root) => {
    const references = new Set();
    const definitions = new Set();
    
    return {
      element: {
        enter(node) {
          // Track defined IDs
          if (node.attributes.id) {
            definitions.add(node.attributes.id);
          }
          
          // Find references in attributes
          Object.entries(node.attributes).forEach(([attr, value]) => {
            // Quick check for URL references
            if (includesUrlReference(value)) {
              console.log(`Found URL reference in ${attr}:`, value);
            }
            
            // Extract specific reference IDs
            const refs = findReferences(attr, value);
            refs.forEach(ref => references.add(ref));
          });
        }
      },
      root: {
        exit() {
          const unusedDefs = [...definitions].filter(id => !references.has(id));
          const brokenRefs = [...references].filter(id => !definitions.has(id));
          
          console.log('Reference Analysis:', {
            definitions: definitions.size,
            references: references.size,
            unusedDefinitions: unusedDefs,
            brokenReferences: brokenRefs
          });
        }
      }
    };
  }
};

// Example: Check various attribute types
const checkElementReferences = (node) => {
  Object.entries(node.attributes).forEach(([attr, value]) => {
    const refs = findReferences(attr, value);
    
    if (refs.length > 0) {
      console.log(`${attr}="${value}" references:`, refs);
    }
    
    // Examples of what gets detected:
    // fill="url(#gradient1)" → ['gradient1']
    // href="#symbol1" → ['symbol1'] 
    // begin="rect1.click" → ['rect1']
    // style="fill: url('#pattern1')" → ['pattern1']
  });
};

// Example: Style processing with reference awareness
const processStyles = (styleValue) => {
  if (includesUrlReference(styleValue)) {
    console.log('Style contains references, preserving:', styleValue);
    return styleValue; // Don't modify styles with references
  }
  
  // Safe to process style without references
  return optimizeStyleValue(styleValue);
};

Numeric Precision

Utility for consistent numeric precision handling.

/**
 * Round number to specified precision without string conversion
 * @param num - Number to round
 * @param precision - Number of decimal places
 * @returns Rounded number (not string like Number.prototype.toFixed)
 */
function toFixed(num: number, precision: number): number;

Usage Examples:

import { toFixed } from "svgo";

// Custom plugin that normalizes numeric values
const numericNormalizationPlugin = {
  name: 'numericNormalization',
  fn: (root, params) => {
    const precision = params.precision || 3;
    
    return {
      element: {
        enter(node) {
          // Normalize coordinate attributes
          ['x', 'y', 'width', 'height', 'cx', 'cy', 'r', 'rx', 'ry'].forEach(attr => {
            if (node.attributes[attr]) {
              const num = parseFloat(node.attributes[attr]);
              if (!isNaN(num)) {
                node.attributes[attr] = toFixed(num, precision).toString();
              }
            }
          });
          
          // Normalize transform values
          if (node.attributes.transform) {
            node.attributes.transform = normalizeTransform(
              node.attributes.transform, 
              precision
            );
          }
          
          // Normalize path data
          if (node.name === 'path' && node.attributes.d) {
            node.attributes.d = normalizePathData(
              node.attributes.d,
              precision
            );
          }
        }
      }
    };
  }
};

// Example: Transform matrix normalization
const normalizeTransform = (transform, precision) => {
  return transform.replace(/[\d.]+/g, (match) => {
    const num = parseFloat(match);
    return toFixed(num, precision).toString();
  });
};

// Example: Path data normalization
const normalizePathData = (pathData, precision) => {
  return pathData.replace(/[\d.-]+/g, (match) => {
    const num = parseFloat(match);
    return toFixed(num, precision).toString();
  });
};

// Example: Consistent numeric operations
const calculateBounds = (elements, precision = 2) => {
  let minX = Infinity, minY = Infinity;
  let maxX = -Infinity, maxY = -Infinity;
  
  elements.forEach(el => {
    const x = parseFloat(el.attributes.x || 0);
    const y = parseFloat(el.attributes.y || 0);
    const width = parseFloat(el.attributes.width || 0);
    const height = parseFloat(el.attributes.height || 0);
    
    minX = Math.min(minX, x);
    minY = Math.min(minY, y);
    maxX = Math.max(maxX, x + width);
    maxY = Math.max(maxY, y + height);
  });
  
  return {
    x: toFixed(minX, precision),
    y: toFixed(minY, precision),
    width: toFixed(maxX - minX, precision),
    height: toFixed(maxY - minY, precision)
  };
};

// Example: Comparing with Number.prototype.toFixed
const compareToFixed = (num, precision) => {
  const svgoResult = toFixed(num, precision);      // Returns number
  const nativeResult = Number(num.toFixed(precision)); // String → number
  
  console.log('Input:', num);
  console.log('SVGO toFixed:', svgoResult, typeof svgoResult);
  console.log('Native toFixed:', nativeResult, typeof nativeResult);
  console.log('Equal:', svgoResult === nativeResult);
};

// Usage in plugin parameter processing
const processPluginParams = (params, precision) => {
  const processed = {};
  
  Object.entries(params).forEach(([key, value]) => {
    if (typeof value === 'number') {
      processed[key] = toFixed(value, precision);
    } else {
      processed[key] = value;
    }
  });
  
  return processed;
};

Advanced Utility Combinations

Examples combining multiple utility functions for complex processing.

// Complex plugin using all utility functions
const comprehensivePlugin = {
  name: 'comprehensive',
  fn: (root, params) => {
    const precision = params.precision || 2;
    const preserveReferences = params.preserveReferences !== false;
    const skipScripts = params.skipScripts !== false;
    
    const referenceIds = new Set();
    const definedIds = new Set();
    
    return {
      element: {
        enter(node, parent) {
          // Security check
          if (skipScripts && hasScripts(node)) {
            console.log(`Skipping ${node.name} - contains scripts`);
            return visitSkip;
          }
          
          // Track ID definitions
          if (node.attributes.id) {
            definedIds.add(node.attributes.id);
          }
          
          // Process attributes
          Object.entries(node.attributes).forEach(([attr, value]) => {
            // Track references if preservation is enabled
            if (preserveReferences) {
              const refs = findReferences(attr, value);
              refs.forEach(ref => referenceIds.add(ref));
              
              if (includesUrlReference(value)) {
                return; // Skip processing attributes with references
              }
            }
            
            // Normalize numeric attributes
            if (['x', 'y', 'width', 'height', 'r', 'cx', 'cy'].includes(attr)) {
              const num = parseFloat(value);
              if (!isNaN(num)) {
                node.attributes[attr] = toFixed(num, precision).toString();
              }
            }
          });
        }
      },
      root: {
        exit() {
          if (preserveReferences) {
            const unusedIds = [...definedIds].filter(id => !referenceIds.has(id));
            if (unusedIds.length > 0) {
              console.log('Unused IDs found:', unusedIds);
            }
          }
        }
      }
    };
  }
};

Install with Tessl CLI

npx tessl i tessl/npm-svgo

docs

ast-manipulation.md

cli.md

configuration.md

core-optimization.md

data-uri.md

index.md

plugins.md

utility-functions.md

tile.json