Merge SVG Documents

Combine multiple SVG files into one document, or import elements from one document into another.

Merge multiple files

Document::merge() takes an array of documents and returns a new merged document. The default strategy appends all children from each source into a single <svg> root.

<?php

use Atelier\Svg\Document;
use Atelier\Svg\Document\MergeStrategy;
use Atelier\Svg\Loader\DomLoader;
use Atelier\Svg\Dumper\CompactXmlDumper;

$loader = new DomLoader();

$base   = $loader->loadFromFile('chart-base.svg');
$labels = $loader->loadFromFile('chart-labels.svg');
$legend = $loader->loadFromFile('chart-legend.svg');

$merged = Document::merge([$base, $labels, $legend]);

$dumper = new CompactXmlDumper();
$dumper->dumpToFile($merged, 'chart-complete.svg');

Layout strategies

Pass a strategy option to control how documents are arranged.

Strategy Effect
MergeStrategy::APPEND All children stacked in the same coordinate space (default)
MergeStrategy::SIDE_BY_SIDE Documents placed left-to-right, separated by optional spacing
MergeStrategy::STACKED Documents placed top-to-bottom, separated by optional spacing
MergeStrategy::SYMBOLS Each document becomes a <symbol> inside <defs>
MergeStrategy::GRID Documents placed in a grid with configurable columns
<?php

use Atelier\Svg\Document;
use Atelier\Svg\Document\MergeStrategy;
use Atelier\Svg\Loader\DomLoader;

$loader = new DomLoader();
$docs = array_map(
    fn ($file) => $loader->loadFromFile($file),
    glob('icons/*.svg'),
);

// Lay out icons in a 4-column grid with 8px spacing
$sheet = Document::merge($docs, [
    'strategy' => MergeStrategy::GRID,
    'columns'  => 4,
    'spacing'  => 8.0,
]);

Build a sprite sheet

MergeStrategy::SYMBOLS wraps each document in a <symbol> element. Pass symbol_ids to assign stable IDs.

<?php

use Atelier\Svg\Document;
use Atelier\Svg\Document\MergeStrategy;
use Atelier\Svg\Loader\DomLoader;
use Atelier\Svg\Dumper\CompactXmlDumper;

$loader = new DomLoader();

$files = [
    'home'   => 'icons/home.svg',
    'user'   => 'icons/user.svg',
    'search' => 'icons/search.svg',
];

$docs = [];
$ids  = [];
foreach ($files as $id => $file) {
    $docs[] = $loader->loadFromFile($file);
    $ids[]  = $id;
}

$sprite = Document::merge($docs, [
    'strategy'   => MergeStrategy::SYMBOLS,
    'symbol_ids' => $ids,
]);

(new CompactXmlDumper())->dumpToFile($sprite, 'sprite.svg');

Reference a symbol in HTML:

<svg width="24" height="24"><use href="sprite.svg#home"></use></svg>

Import individual elements

Document::importElement() clones an element from one document and returns an imported copy ready to append anywhere in the target document.

<?php

use Atelier\Svg\Document;
use Atelier\Svg\Loader\DomLoader;

$loader = new DomLoader();
$target = $loader->loadFromFile('canvas.svg');
$source = $loader->loadFromFile('overlay.svg');

// Find and import a single element by selector
$arrow = $source->querySelector('#arrow');
if ($arrow !== null) {
    $imported = $target->importElement($arrow, deep: true);
    $target->getRootElement()->appendChild($imported);
}

To import several elements at once:

<?php

$elements = $source->querySelectorAll('.badge')->toArray();
$imported = $target->importElements($elements, deep: true);

foreach ($imported as $el) {
    $target->getRootElement()->appendChild($el);
}

Append a whole document

Document::append() imports all top-level children from another document into the current one. This modifies the receiver in place.

<?php

$base->append($overlay);

ID conflicts

When sources share IDs (gradients, clip-paths, filters), merging without handling them causes elements to silently reference the wrong definition.

Automatic resolution - the default. Conflicting IDs are renamed with a generated suffix:

<?php

$merged = Document::merge([$docA, $docB]);
// IDs are resolved automatically

Explicit prefix - gives each document's IDs a deterministic namespace:

<?php

$merged = Document::merge([$docA, $docB], [
    'prefix_ids' => true,   // adds "doc0-", "doc1-", etc.
]);

// Or supply a fixed string prefix for importElement:
$imported = $target->importElement($element, deep: true, options: [
    'prefix_ids' => 'overlay-',
]);

viewBox differences

Document::merge() does not reconcile different viewBoxes. Under APPEND, all content lands in the same coordinate space - elements sized for a 0 0 24 24 viewBox will overlap elements sized for 0 0 100 100. Use SIDE_BY_SIDE or STACKED to let the merge code wrap each document in a translated <g> that preserves its original positioning, or apply a transform manually after importing.

See also