#!/usr/bin/env python
# pylint: disable=too-many-lines

# /***************************************************************************
# symbols.py
# ----------
# Date                 : October 2019
# copyright            : (C) 2019 by Nyall Dawson
# email                : nyall.dawson@gmail.com
#
#  ***************************************************************************/
#
# /***************************************************************************
#  *                                                                         *
#  *   This program is free software; you can redistribute it and/or modify  *
#  *   it under the terms of the GNU General Public License as published by  *
#  *   the Free Software Foundation; either version 2 of the License, or     *
#  *   (at your option) any later version.                                   *
#  *                                                                         *
#  ***************************************************************************/

"""
Symbol converter
"""

import base64
import math
import os
import shutil
from shutil import SameFileError
import subprocess
import uuid
from pathlib import Path
from typing import Union, Tuple

from qgis.core import (
    qgsDoubleNear,
    Qgis,
    QgsGeometry,
    QgsUnitTypes,
    QgsSimpleLineSymbolLayer,
    QgsSimpleFillSymbolLayer,
    QgsFillSymbol,
    QgsLineSymbol,
    QgsMarkerSymbol,
    QgsEllipseSymbolLayer,
    QgsSimpleMarkerSymbolLayer,
    QgsSvgMarkerSymbolLayer,
    QgsSVGFillSymbolLayer,
    QgsSimpleMarkerSymbolLayerBase,
    QgsFontMarkerSymbolLayer,
    QgsGradientColorRamp,
    QgsMarkerLineSymbolLayer,
    QgsLinePatternFillSymbolLayer,
    QgsRasterFillSymbolLayer,
    QgsGradientFillSymbolLayer,
    QgsGradientStop,
    QgsShapeburstFillSymbolLayer,
    QgsRectangle,
    QgsPointPatternFillSymbolLayer,
    QgsSymbol,
    QgsTemplatedLineSymbolLayerBase,
    QgsHashedLineSymbolLayer,
    QgsRandomMarkerFillSymbolLayer,
)

try:
    from qgis.core import QgsRasterMarkerSymbolLayer
except ImportError:
    pass

from qgis.PyQt.QtCore import Qt, QPointF, QLineF, QBuffer
from qgis.PyQt.QtGui import (
    QColor,
    QFont,
    QFontMetricsF,
    QPainterPath,
    QPainter,
    QBrush,
    QTransform,
)
from qgis.PyQt.QtSvg import QSvgGenerator, QSvgRenderer
from qgis.PyQt.QtXml import QDomDocument

from ..parser.objects.multi_layer_symbols import (
    MultiLayerSymbol,
    MultiLayerFillSymbol,
    MultiLayerLineSymbol,
    MultiLayerMarkerSymbol,
)
from ..parser.objects.symbol_layer import SymbolLayer
from ..parser.objects.line_symbol_layer import (
    SimpleLineSymbol,
    CartographicLineSymbol,
    MarkerLineSymbol,
    HashLineSymbol,
    LineSymbolLayer,
    PictureLineSymbol,
)
from ..parser.objects.fill_symbol_layer import (
    FillSymbolLayer,
    SimpleFillSymbol,
    ColorSymbol,
    LineFillSymbol,
    MarkerFillSymbol,
    PictureFillSymbol,
    GradientFillSymbol,
)
from ..parser.objects.marker_symbol_layer import (
    MarkerSymbolLayer,
    SimpleMarkerSymbol,
    ArrowMarkerSymbol,
    CharacterMarkerSymbol,
    PictureMarkerSymbol,
)
from ..parser.objects.dot_density_fill_symbol import DotDensityFillSymbol
from ..parser.objects.text_symbol import TextSymbol
from ..parser.objects.label_style import LabelStyle
from ..parser.objects.maplex_label_style import MaplexLabelStyle
from ..parser.exceptions import NotImplementedException
from .context import Context
from .color import ColorConverter
from .decorations import DecorationConverter
from ..parser.objects.simple_line3d_symbol import SimpleLine3DSymbol
from ..parser.objects.marker3d_symbol import Marker3DSymbol
from ..parser.objects.simple_marker3d_symbol import SimpleMarker3DSymbol
from ..parser.objects.character_marker3d_symbol import CharacterMarker3DSymbol
from ..parser.objects.texture_fill_symbol import TextureFillSymbol
from ..parser.objects.color_ramp_symbol import ColorRampSymbol
from ..parser.objects.ramps import ColorRamp
from ..parser.objects.symbol_shadow import SymbolShadow
from ..parser.objects.symbol_background import SymbolBackground
from ..parser.objects.symbol_border import SymbolBorder


from .text_format import TextSymbolConverter
from .labels import LabelConverter
from .color_ramp import ColorRampConverter
from .utils import ConversionUtils
from .pictures import PictureUtils
from ..parser.objects.picture import EmfPicture, StdPicture, BmpPicture
from ..bintools.file_utils import FileUtils


class SymbolConverter:  # pylint: disable=too-many-public-methods
    """
    Converts ArcObjects symbols to QGIS symbols
    """

    @staticmethod
    def null_symbol(symbol_type):
        """
        Returns a "null" symbol of the desired type
        """
        if symbol_type == QgsSymbol.SymbolType.Line:
            out = QgsLineSymbol()
            layer = QgsSimpleLineSymbolLayer(penStyle=Qt.PenStyle.NoPen)
            out.changeSymbolLayer(0, layer)
        else:
            assert False
        return out

    # pylint: disable=too-many-return-statements, too-many-branches, too-many-statements
    @staticmethod
    def Symbol_to_QgsSymbol(symbol, context: Context):
        """
        Converts a raw Symbol to a QgsSymbol
        """
        if not symbol:
            return None

        old_symbol_layer_output_to_input_index_map = (
            context.symbol_layer_output_to_input_index_map
        )
        old_current_symbol_layer = context.current_symbol_layer

        context.symbol_layer_output_to_input_index_map = {}

        if issubclass(
            symbol.__class__,
            (
                MultiLayerFillSymbol,
                FillSymbolLayer,
                TextureFillSymbol,
            ),
        ):
            out = QgsFillSymbol()
        elif issubclass(
            symbol.__class__,
            (MultiLayerLineSymbol, LineSymbolLayer, SimpleLine3DSymbol),
        ):
            out = QgsLineSymbol()
        elif issubclass(
            symbol.__class__,
            (
                MultiLayerMarkerSymbol,
                MarkerSymbolLayer,
                Marker3DSymbol,
                SimpleMarker3DSymbol,
                CharacterMarker3DSymbol,
            ),
        ):
            out = QgsMarkerSymbol()
        elif issubclass(symbol.__class__, ColorRamp):
            out = ColorRampConverter.ColorRamp_to_QgsColorRamp(symbol)
            context.final_symbol_layer_output_to_input_index_map = (
                context.symbol_layer_output_to_input_index_map
            )
            context.symbol_layer_output_to_input_index_map = (
                old_symbol_layer_output_to_input_index_map
            )
            context.current_symbol_layer = old_current_symbol_layer
            return out
        elif issubclass(symbol.__class__, ColorRampSymbol):
            out = QgsFillSymbol()
            SymbolConverter.append_GradientFillSymbolLayer(out, symbol, context)
            out.deleteSymbolLayer(0)
            context.final_symbol_layer_output_to_input_index_map = (
                context.symbol_layer_output_to_input_index_map
            )
            context.symbol_layer_output_to_input_index_map = (
                old_symbol_layer_output_to_input_index_map
            )
            context.current_symbol_layer = old_current_symbol_layer
            return out
        elif issubclass(symbol.__class__, DotDensityFillSymbol):
            out = QgsFillSymbol()
            SymbolConverter.append_DotDensityFillSymbolLayer(out, symbol, context)
            out.deleteSymbolLayer(0)
            context.final_symbol_layer_output_to_input_index_map = (
                context.symbol_layer_output_to_input_index_map
            )
            context.symbol_layer_output_to_input_index_map = (
                old_symbol_layer_output_to_input_index_map
            )
            context.current_symbol_layer = old_current_symbol_layer
            return out
        elif issubclass(symbol.__class__, TextSymbol):
            context.final_symbol_layer_output_to_input_index_map = (
                context.symbol_layer_output_to_input_index_map
            )
            context.symbol_layer_output_to_input_index_map = (
                old_symbol_layer_output_to_input_index_map
            )
            context.current_symbol_layer = old_current_symbol_layer
            return TextSymbolConverter.text_symbol_to_qgstextformat(symbol, context)
        elif issubclass(symbol.__class__, LabelStyle) or issubclass(
            symbol.__class__, MaplexLabelStyle
        ):
            context.final_symbol_layer_output_to_input_index_map = (
                context.symbol_layer_output_to_input_index_map
            )
            context.symbol_layer_output_to_input_index_map = (
                old_symbol_layer_output_to_input_index_map
            )
            context.current_symbol_layer = old_current_symbol_layer
            return LabelConverter.convert_label_style(symbol, context)
        else:
            context.final_symbol_layer_output_to_input_index_map = (
                context.symbol_layer_output_to_input_index_map
            )
            context.symbol_layer_output_to_input_index_map = (
                old_symbol_layer_output_to_input_index_map
            )
            context.current_symbol_layer = old_current_symbol_layer

            raise NotImplementedException(
                "{} symbols cannot be converted yet".format(symbol.__class__.__name__),
                symbol,
            )

        old_current_symbol = context.current_symbol

        context.current_symbol = symbol
        context.current_symbol_layer = None
        SymbolConverter.add_symbol_layers(out, symbol, context)

        if (
            isinstance(symbol, MultiLayerMarkerSymbol) and symbol.halo
        ) and Qgis.QGIS_VERSION_INT >= 33900:
            from qgis.core import QgsSymbolBufferSettings

            buffer_settings = QgsSymbolBufferSettings()
            buffer_settings.setEnabled(True)

            buffer_settings.setSize(context.convert_size(symbol.halo_size))
            buffer_settings.setSizeUnit(context.units)

            buffer_fill = SymbolConverter.Symbol_to_QgsSymbol(
                symbol.halo_symbol, context
            )
            buffer_settings.setFillSymbol(buffer_fill)

            out.setBufferSettings(buffer_settings)
        elif (isinstance(symbol, MultiLayerMarkerSymbol) and symbol.halo) or (False):
            context.push_warning(
                "Marker halos are only supported on QGIS 3.40 or later"
            )

        context.final_symbol_layer_output_to_input_index_map = (
            context.symbol_layer_output_to_input_index_map
        )
        context.symbol_layer_output_to_input_index_map = (
            old_symbol_layer_output_to_input_index_map
        )
        context.current_symbol = old_current_symbol
        context.current_symbol_layer = old_current_symbol_layer

        return out

    # pylint: enable=too-many-return-statements, too-many-branches, too-many-statements

    @staticmethod
    def symbol_to_color(symbol, context: Context):
        """
        Converts an ESRI symbol to a single color best representing the symbol
        """
        s = SymbolConverter.Symbol_to_QgsSymbol(symbol, context)
        if issubclass(symbol.__class__, MultiLayerFillSymbol):
            all_null = True
            for layer in symbol.layers:
                if (
                    isinstance(layer, SimpleFillSymbol)
                    and symbol.layers[0].fill_style == SimpleFillSymbol.STYLE_NULL
                ):
                    continue
                all_null = False
            if all_null:
                c = s.color()
                c.setAlpha(0)
                return c
            else:
                return s.color()
        elif isinstance(symbol, SimpleFillSymbol):
            if symbol.fill_style == SimpleFillSymbol.STYLE_NULL:
                c = s.color()
                c.setAlpha(0)
                return c
            return s.color()
        elif s is None:
            c = QColor(0, 0, 0)
            c.setAlpha(0)
            return c

        return s.color()

    @staticmethod
    def symbol_to_fill_color(symbol, context: Context):
        """
        Converts an ESRI symbol to a single color best representing the symbol's fill color
        """
        s = SymbolConverter.Symbol_to_QgsSymbol(symbol, context)
        if isinstance(s, QgsFillSymbol):
            for i in range(s.symbolLayerCount()):
                layer = s.symbolLayer(i)
                if (
                    isinstance(layer, QgsSimpleFillSymbolLayer)
                    and layer.brushStyle() != Qt.BrushStyle.NoBrush
                ):
                    if layer.color().alpha() > 0:
                        return layer.color()
        return None

    @staticmethod
    def symbol_to_line_color(symbol, context: Context):
        """
        Converts an ESRI symbol to a single color best representing the symbol's line color
        """
        s = SymbolConverter.Symbol_to_QgsSymbol(symbol, context)
        if isinstance(s, QgsFillSymbol):
            for i in range(s.symbolLayerCount()):
                layer = s.symbolLayer(i)
                if (
                    isinstance(layer, QgsSimpleFillSymbolLayer)
                    and layer.strokeStyle() != Qt.PenStyle.NoPen
                ):
                    return layer.strokeColor()
                elif isinstance(layer, QgsSimpleLineSymbolLayer):
                    return layer.color()
            return None
        else:
            return s.color()

    @staticmethod
    def symbol_to_line_width(symbol, context: Context) -> float:
        """
        Converts an ESRI symbol to a single line width value best representing the symbol
        """
        s = SymbolConverter.Symbol_to_QgsSymbol(symbol, context)
        if isinstance(s, QgsFillSymbol):
            for i in range(s.symbolLayerCount()):
                layer = s.symbolLayer(i)
                if (
                    isinstance(layer, QgsSimpleFillSymbolLayer)
                    and layer.strokeStyle() != Qt.PenStyle.NoPen
                ):
                    return layer.strokeWidth()
                elif isinstance(layer, QgsSimpleLineSymbolLayer):
                    return layer.width()
            return None
        else:
            return s.width()

    @staticmethod
    def symbol_to_marker_size(symbol, context: Context) -> float:
        """
        Converts an ESRI symbol to a single marker size value best representing the symbol
        """
        s = SymbolConverter.Symbol_to_QgsSymbol(symbol, context)
        return s.size()

    @staticmethod
    def symbol_to_marker_shape(symbol, context: Context) -> str:  # pylint: disable=unused-argument
        """
        Converts an ESRI symbol to a marker shape string ("circle" or "square") best representing the symbol
        """
        if isinstance(symbol, SimpleMarkerSymbol):
            return symbol.type

        elif (
            isinstance(symbol, MultiLayerMarkerSymbol)
            and len(symbol.layers) == 1
            and isinstance(symbol.layers[0], SimpleMarkerSymbol)
        ):
            return symbol.layers[0].type

        return "circle"

    @staticmethod
    def add_symbol_layers(out, symbol, context: Context):
        """
        Adds all symbol layers to a symbol
        """
        out.deleteSymbolLayer(0)
        if issubclass(symbol.__class__, SymbolLayer):
            context.current_symbol_layer = 0
            SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(out, symbol, context)
        else:
            layers = symbol.layers
            prev_use_real_world_symbol_sizes = context.use_real_world_units

            for idx, layer in enumerate(layers):
                context.current_symbol_layer = idx
                SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                    out, layer, context
                )

            if True and symbol.symbol_level != 0xFFFFFFFF:
                # 0xffffffff = sub layers have own level
                for i in range(out.symbolLayerCount()):
                    out.symbolLayer(i).setRenderingPass(symbol.symbol_level)

            context.use_real_world_units = prev_use_real_world_symbol_sizes
            context.global_cim_effects = []

        if out.symbolLayerCount() == 0:
            # we appended nothing! Add a dummy invisible layer to avoid QGIS adding a default layer to this
            if isinstance(out, QgsMarkerSymbol):
                layer = QgsSimpleMarkerSymbolLayer(color=QColor(0, 0, 0, 0))
                layer.setStrokeStyle(Qt.PenStyle.NoPen)
                out.appendSymbolLayer(layer)
                context.symbol_layer_output_to_input_index_map[layer] = (
                    context.current_symbol_layer
                )
            elif isinstance(out, QgsLineSymbol):
                layer = QgsSimpleLineSymbolLayer(
                    color=QColor(0, 0, 0, 0), penStyle=Qt.PenStyle.NoPen
                )
                out.appendSymbolLayer(layer)
                context.symbol_layer_output_to_input_index_map[layer] = (
                    context.current_symbol_layer
                )
            elif isinstance(out, QgsFillSymbol):
                layer = QgsSimpleFillSymbolLayer(
                    color=QColor(0, 0, 0, 0),
                    style=Qt.BrushStyle.NoBrush,
                    strokeStyle=Qt.PenStyle.NoPen,
                )
                out.appendSymbolLayer(layer)
                context.symbol_layer_output_to_input_index_map[layer] = (
                    context.current_symbol_layer
                )

        context.current_symbol_layer = None
        return out

    @staticmethod
    def append_SymbolLayer_to_QgsSymbolLayer(
        symbol,  # pylint: disable=too-many-statements,too-many-branches
        layer,
        context: Context,
    ):
        """
        Appends a SymbolLayer to a QgsSymbolLayer
        """
        if issubclass(
            layer.__class__,
            (SymbolLayer,),
        ):
            if issubclass(layer.__class__, FillSymbolLayer):
                SymbolConverter.append_FillSymbolLayer(symbol, layer, context)
            elif issubclass(layer.__class__, LineSymbolLayer):
                SymbolConverter.append_LineSymbolLayer(symbol, layer, context)
            elif issubclass(layer.__class__, MarkerSymbolLayer):
                SymbolConverter.append_MarkerSymbolLayer(symbol, layer, context)
            elif issubclass(layer.__class__, Marker3DSymbol):
                SymbolConverter.append_Marker3DSymbolLayer(symbol, layer, context)
            elif issubclass(layer.__class__, SimpleMarker3DSymbol):
                SymbolConverter.append_SimpleMarker3DSymbolLayer(symbol, layer, context)
            elif issubclass(layer.__class__, SimpleLine3DSymbol):
                SymbolConverter.append_SimpleLine3DSymbolLayer(symbol, layer, context)
            elif issubclass(layer.__class__, CharacterMarker3DSymbol):
                SymbolConverter.append_CharacterMarker3DSymbolLayer(
                    symbol, layer, context
                )
            elif issubclass(layer.__class__, TextureFillSymbol):
                SymbolConverter.append_TextureFillSymbolLayer(symbol, layer, context)
            elif issubclass(layer.__class__, ColorRampSymbol):
                SymbolConverter.append_FillSymbolLayer(symbol, layer, context)
            else:
                raise NotImplementedException(
                    "Converting {} not implemented yet".format(layer.__class__.__name__)
                )
        else:
            for sublayer in layer.layers:
                SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                    symbol, sublayer, context
                )

    @staticmethod
    def append_FillSymbolLayer(symbol, layer, context: Context):
        """
        Appends a FillSymbolLayer to a symbol
        """
        if isinstance(layer, (SimpleFillSymbol, ColorSymbol)):
            SymbolConverter.append_SimpleFillSymbolLayer(symbol, layer, context)
        elif isinstance(layer, LineFillSymbol):
            SymbolConverter.append_LineFillSymbolLayer(symbol, layer, context)
        elif isinstance(layer, MarkerFillSymbol):
            SymbolConverter.append_MarkerFillSymbolLayer(symbol, layer, context)
        elif isinstance(layer, PictureFillSymbol):
            SymbolConverter.append_PictureFillSymbolLayer(symbol, layer, context)
        elif isinstance(layer, DotDensityFillSymbol):
            SymbolConverter.append_DotDensityFillSymbolLayer(symbol, layer, context)
        elif isinstance(layer, GradientFillSymbol):
            SymbolConverter.append_GradientFillSymbolLayer(symbol, layer, context)
        else:
            raise NotImplementedException(
                "Converting {} not implemented yet".format(layer.__class__.__name__)
            )

    @staticmethod
    def append_LineSymbolLayer(symbol, layer, context: Context):
        """
        Appends a LineSymbolLayer to a QgsSymbol
        """
        if isinstance(layer, SimpleLineSymbol):
            SymbolConverter.append_SimpleLineSymbolLayer(symbol, layer, context)
        elif isinstance(layer, CartographicLineSymbol):
            SymbolConverter.append_CartographicLineSymbolLayer(symbol, layer, context)
        elif isinstance(layer, (MarkerLineSymbol, HashLineSymbol)):
            SymbolConverter.append_TemplatedLineSymbolLayer(symbol, layer, context)
        elif isinstance(layer, PictureLineSymbol):
            SymbolConverter.append_PictureLineSymbolLayer(symbol, layer, context)
        else:
            raise NotImplementedException(
                "Converting {} not implemented yet".format(layer.__class__.__name__)
            )

    @staticmethod
    def append_MarkerSymbolLayer(symbol, layer, context: Context):
        """
        Appends a MarkerSymbolLayer to a QgsSymbol
        """
        if isinstance(layer, SimpleMarkerSymbol):
            SymbolConverter.append_SimpleMarkerSymbolLayer(symbol, layer, context)
        elif isinstance(layer, ArrowMarkerSymbol):
            SymbolConverter.append_ArrowMarkerSymbolLayer(symbol, layer, context)
        elif isinstance(layer, CharacterMarkerSymbol):
            SymbolConverter.append_CharacterMarkerSymbolLayer(symbol, layer, context)
        elif isinstance(layer, PictureMarkerSymbol):
            SymbolConverter.append_PictureMarkerSymbolLayer(symbol, layer, context)
        else:
            raise NotImplementedException(
                "Converting {} not implemented yet".format(layer.__class__.__name__)
            )

    @staticmethod
    def append_SimpleFillSymbolLayer(
        symbol,  # pylint: disable=too-many-branches,too-many-statements
        layer: SimpleFillSymbol,
        context: Context,
    ):
        """
        Appends a SimpleFillSymbolLayer to a symbol
        """
        fill_color = ColorConverter.color_to_qcolor(layer.color)
        out = QgsSimpleFillSymbolLayer(fill_color)
        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)

        if isinstance(layer, SimpleFillSymbol):
            if layer.outline and not isinstance(layer.outline, MultiLayerLineSymbol):
                # these properties are not supported in QGIS simple fill, so we need
                # to add an additional outline layer to support them
                uses_complex_outline = (
                    (hasattr(layer.outline, "offset") and layer.outline.offset)
                    or (
                        hasattr(layer.outline, "template")
                        and layer.outline.template
                        and len(layer.outline.template.pattern_parts) > 0
                    )
                    or (
                        hasattr(layer.outline, "decoration")
                        and layer.outline.decoration
                    )
                )
                if not uses_complex_outline:
                    # great, we can avoid the extra symbol layer!
                    if isinstance(
                        layer.outline, (SimpleLineSymbol, CartographicLineSymbol)
                    ):
                        out.setStrokeColor(
                            ColorConverter.color_to_qcolor(layer.outline.color)
                        )
                        out.setStrokeWidth(
                            context.convert_size(
                                context.fix_line_width(layer.outline.width)
                            )
                        )
                        out.setStrokeWidthUnit(context.units)
                    if isinstance(layer.outline, SimpleLineSymbol):
                        out.setStrokeStyle(
                            ConversionUtils.symbol_pen_to_qpenstyle(
                                layer.outline.line_type
                            )
                            if layer.outline.width > 0
                            else Qt.PenStyle.NoPen
                        )
                        out.setPenJoinStyle(Qt.PenJoinStyle.RoundJoin)
                    if isinstance(layer.outline, CartographicLineSymbol):
                        out.setPenJoinStyle(
                            ConversionUtils.symbol_pen_to_qpenjoinstyle(
                                layer.outline.join
                            )
                        )
                        if layer.outline.width <= 0:
                            out.setStrokeStyle(Qt.PenStyle.NoPen)
                    # better matching of null stroke color to QGIS symbology
                    if out.strokeColor().alpha() == 0:
                        out.setStrokeStyle(Qt.PenStyle.NoPen)

                    if (
                        context.apply_conversion_tweaks
                        and (
                            out.strokeColor().alpha() == 0
                            or out.strokeStyle() == Qt.PenStyle.NoPen
                        )
                        and (out.color().alpha() == 0)
                    ):
                        # skip empty layers
                        return

                    symbol.appendSymbolLayer(out)
                else:
                    out.setStrokeStyle(Qt.PenStyle.NoPen)

                    if not context.apply_conversion_tweaks or out.color().alpha() != 0:
                        symbol.appendSymbolLayer(out)

                    SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                        symbol, layer.outline, context
                    )
            elif isinstance(layer.outline, MultiLayerLineSymbol):
                # outline is a symbol itself
                out.setStrokeStyle(Qt.PenStyle.NoPen)

                if not context.apply_conversion_tweaks or out.color().alpha() != 0:
                    symbol.appendSymbolLayer(out)

                # get all layers from outline
                SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                    symbol, layer.outline, context
                )
            else:
                out.setStrokeStyle(Qt.PenStyle.NoPen)

                if not context.apply_conversion_tweaks or out.color().alpha() != 0:
                    symbol.appendSymbolLayer(out)

            if layer.fill_style == SimpleFillSymbol.STYLE_NULL:
                out.setBrushStyle(Qt.BrushStyle.NoBrush)
            elif layer.fill_style == SimpleFillSymbol.STYLE_HORIZONTAL:
                out.setBrushStyle(Qt.BrushStyle.HorPattern)
            elif layer.fill_style == SimpleFillSymbol.STYLE_VERTICAL:
                out.setBrushStyle(Qt.BrushStyle.VerPattern)
            elif layer.fill_style == SimpleFillSymbol.STYLE_FORWARD_DIAGONAL:
                out.setBrushStyle(Qt.BrushStyle.FDiagPattern)
            elif layer.fill_style == SimpleFillSymbol.STYLE_BACKWARD_DIAGONAL:
                out.setBrushStyle(Qt.BrushStyle.BDiagPattern)
            elif layer.fill_style == SimpleFillSymbol.STYLE_CROSS:
                out.setBrushStyle(Qt.BrushStyle.CrossPattern)
            elif layer.fill_style == SimpleFillSymbol.STYLE_DIAGONAL_CROSS:
                out.setBrushStyle(Qt.BrushStyle.DiagCrossPattern)

    @staticmethod
    def append_LineFillSymbolLayer(
        symbol,
        layer: LineFillSymbol,
        context: Context,
    ):
        """
        Appends a LineFillSymbolLayer to a symbol
        """

        line = SymbolConverter.Symbol_to_QgsSymbol(layer.line, context)

        out = QgsLinePatternFillSymbolLayer()
        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)
        out.setSubSymbol(line)
        out.setLineAngle(layer.angle or 0)

        separation = layer.separation
        # if context.apply_conversion_tweaks:
        #    min_separation = 5 * max(line.width(),
        #                             0.5 if line.symbolLayer(0).widthUnit() == QgsUnitTypes.RenderPoints else 0.18)
        #    separation = max(min_separation, separation)
        #    if context.units == QgsUnitTypes.RenderMillimeters:
        #        separation *= 0.352

        out.setDistance(context.convert_size(separation))
        out.setDistanceUnit(context.units)

        out.setOffset(context.convert_size(layer.offset))
        out.setOffsetUnit(context.units)

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

        if isinstance(layer.outline, MultiLayerLineSymbol):
            # get all layers from outline
            SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                symbol, layer.outline, context
            )
        elif layer.outline:
            SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                symbol, layer.outline, context
            )

    @staticmethod
    def append_GradientFillSymbolLayer(
        symbol,  # pylint: disable=too-many-statements,too-many-branches
        layer: Union[GradientFillSymbol, ColorRampSymbol],
        context: Context,
    ):
        """
        Appends a GradientFillSymbolLayer to a symbol
        """

        if isinstance(layer, ColorRampSymbol):
            ramp = ColorRampConverter.ColorRamp_to_QgsColorRamp(layer.color_ramp)
        else:
            ramp = ColorRampConverter.ColorRamp_to_QgsColorRamp(layer.ramp)

        def scale_ramp(ramp, percent):
            """
            Scales a ramp by the percent factor
            """
            # percent has a different meaning here -- it effectively stretches the ramp
            if percent < 1:
                # only support this for gradient ramps
                if isinstance(ramp, QgsGradientColorRamp):
                    stops = ramp.stops()
                    for s in stops:
                        s.offset = 1 - percent * (1 - s.offset)
                    # also need to insert a new first color stop
                    stops = [QgsGradientStop(1 - percent, ramp.color1())] + stops
                    ramp.setStops(stops)

        if isinstance(layer, GradientFillSymbol) and layer.type in (
            GradientFillSymbol.RECTANGULAR,
            GradientFillSymbol.BUFFERED,
        ):
            context.push_warning(
                "Rectangular gradients are not supported in QGIS, "
                "using buffered gradient instead"
            )

            out = QgsShapeburstFillSymbolLayer()
            out.setColorType(QgsShapeburstFillSymbolLayer.ShapeburstColorType.ColorRamp)
            ramp.invert()
            scale_ramp(ramp, layer.percent)
            ramp.invert()
            out.setColorRamp(ramp)

            out.setEnabled(layer.enabled)
            out.setLocked(layer.locked)
            if layer.symbol_level != 0xFFFFFFFF:
                out.setRenderingPass(layer.symbol_level)

            symbol.appendSymbolLayer(out)
            context.symbol_layer_output_to_input_index_map[out] = (
                context.current_symbol_layer
            )
            return
        elif (
            isinstance(layer, GradientFillSymbol)
            and layer.type == GradientFillSymbol.CIRCULAR
        ):
            ref_1 = QPointF(0.5, 0.5)
            ref_2 = QPointF(1.0, 0.5)
            gradient_type = QgsGradientFillSymbolLayer.GradientType.Radial
            # yep!!
            ramp.invert()
            scale_ramp(ramp, layer.percent)
        elif isinstance(layer, ColorRampSymbol) or (
            isinstance(layer, GradientFillSymbol)
            and layer.type == GradientFillSymbol.LINEAR
        ):
            l1 = QLineF(0.5, 0.5, 1, 0.5)
            l2 = QLineF(0.5, 0.5, 0, 0.5)

            if isinstance(layer, ColorRampSymbol):
                angle = 0 if layer.horizontal else 270
                percent = 100
            else:
                if isinstance(layer, GradientFillSymbol):
                    percent = layer.percent

                angle = layer.angle

            # angle of
            # 0: right to left
            # 90: top to bottom
            # 180: left to right
            # 270: bottom to top
            l1.setAngle(angle)
            l2.setAngle(angle + 180)

            ref_1 = l1.p2()
            ref_2 = l2.p2()

            if percent < 100:
                l1 = QLineF(ref_1, ref_2)
                l1.setLength(l1.length() * percent)
                ref_2 = l1.p2()

            gradient_type = QgsGradientFillSymbolLayer.GradientType.Linear

        out = QgsGradientFillSymbolLayer(
            gradientColorType=QgsGradientFillSymbolLayer.GradientColorType.ColorRamp,
            gradientType=gradient_type,
        )
        out.setColorRamp(ramp)
        out.setReferencePoint1(ref_1)
        out.setReferencePoint2(ref_2)
        if isinstance(layer, GradientFillSymbol):
            out.setEnabled(layer.enabled)
            out.setLocked(layer.locked)
            if (
                isinstance(layer, GradientFillSymbol)
                and layer.symbol_level != 0xFFFFFFFF
            ):
                out.setRenderingPass(layer.symbol_level)

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

    @staticmethod
    def append_MarkerFillSymbolLayer(symbol, layer: MarkerFillSymbol, context: Context):
        """
        Appends a MarkerFillSymbolLayer to a symbol
        """

        marker = SymbolConverter.Symbol_to_QgsSymbol(layer.marker, context)

        if layer.random:
            density = layer.separation_x * layer.separation_y / 10
            if Qgis.QGIS_VERSION_INT >= 32400:
                out = QgsRandomMarkerFillSymbolLayer(
                    1, Qgis.PointCountMethod.DensityBased, density
                )
            else:
                out = QgsRandomMarkerFillSymbolLayer(
                    1,
                    QgsRandomMarkerFillSymbolLayer.CountMethod.DensityBasedCount,
                    density,
                )
            out.setClipPoints(True)
        else:
            out = QgsPointPatternFillSymbolLayer()
            out.setDistanceX(context.convert_size(layer.separation_x))
            out.setDistanceXUnit(context.units)
            out.setDistanceY(context.convert_size(layer.separation_y))
            out.setDistanceYUnit(context.units)

            # Offset only supported since QGIS 3.8
            if hasattr(out, "setOffsetX"):
                out.setOffsetX(layer.offset_x)
                out.setOffsetXUnit(QgsUnitTypes.RenderUnit.RenderPoints)
                out.setOffsetY(layer.offset_y)
                out.setOffsetYUnit(QgsUnitTypes.RenderUnit.RenderPoints)

        out.setSubSymbol(marker)
        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

        if isinstance(layer.outline, MultiLayerLineSymbol):
            # get all layers from outline
            SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                symbol, layer.outline, context
            )
        elif layer.outline:
            SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                symbol, layer.outline, context
            )

    @staticmethod
    def append_DotDensityFillSymbolLayer(
        symbol, layer: DotDensityFillSymbol, context: Context
    ):
        """
        Appends a DotDensityFillSymbol to a symbol
        """

        if len(layer.markers) > 1:
            raise NotImplementedException(
                "{}: Dot density fills with multiple markers are not currently support".format(
                    context.layer_name or context.symbol_name
                )
            )

        marker = SymbolConverter.Symbol_to_QgsSymbol(layer.markers[0], context)

        if Qgis.QGIS_VERSION_INT >= 32400:
            out = QgsRandomMarkerFillSymbolLayer(
                layer.dot_counts[0],
                Qgis.PointCountMethod.Absolute,
            )
        else:
            out = QgsRandomMarkerFillSymbolLayer(
                layer.dot_counts[0],
                QgsRandomMarkerFillSymbolLayer.CountMethod.AbsoluteCount,
            )
        out.setClipPoints(layer.use_mask)
        out.setSeed(layer.seed)

        out.setSubSymbol(marker)
        out.setEnabled(True)
        out.setLocked(False)

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

        if isinstance(layer.outline_symbol, MultiLayerLineSymbol):
            # get all layers from outline
            SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                symbol, layer.outline_symbol, context
            )
        elif layer.outline_symbol:
            SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                symbol, layer.outline_symbol, context
            )

    @staticmethod
    def symbol_name_to_filename(
        symbol_name: str, picture_folder: str, extension: str
    ) -> str:
        """
        Returns a new unique filename for the given symbol to use
        """
        if symbol_name:
            safe_symbol_name = FileUtils.clean_symbol_name_for_file(symbol_name)
        else:
            safe_symbol_name = None

        if not safe_symbol_name:
            safe_symbol_name = str(uuid.uuid4())

        path = os.path.join(picture_folder, safe_symbol_name + "." + extension)
        counter = 1
        while os.path.exists(path):
            path = os.path.join(
                picture_folder, safe_symbol_name + "_" + str(counter) + "." + extension
            )
            counter += 1

        return path

    # pylint: disable=too-many-branches, too-many-statements
    @staticmethod
    def emf_to_svg(
        emf_path: str, svg_path: str, inkscape_path: str = None, context: Context = None
    ):
        """
        Converts an EMF file to an SVG file (using inkscape)
        """
        binary = "inkscape"
        if inkscape_path is not None:
            binary = inkscape_path
        likely_invalid_path = False

        # most recent inkscape version arguments
        export_args = [
            binary,
            "--export-plain-svg",
            "--export-area-drawing",
            "-o",
            svg_path,
            emf_path,
        ]

        # pylint: disable=subprocess-run-check
        CREATE_NO_WINDOW = 0x08000000
        try:
            try:
                _ = subprocess.run(
                    export_args,
                    stdout=subprocess.PIPE,
                    creationflags=CREATE_NO_WINDOW,
                    check=False,
                )
            except ValueError:
                _ = subprocess.run(export_args, stdout=subprocess.PIPE, check=False)
        except FileNotFoundError:
            pass
        except PermissionError:
            likely_invalid_path = True

        if not os.path.exists(svg_path):
            # bah, inkscape changed parameters!
            export_args = [
                binary,
                "--export-plain-svg",
                "--export-file",
                svg_path,
                emf_path,
            ]

            try:
                try:
                    _ = subprocess.run(
                        export_args,
                        stdout=subprocess.PIPE,
                        creationflags=CREATE_NO_WINDOW,
                        check=False,
                    )
                except ValueError:
                    _ = subprocess.run(export_args, stdout=subprocess.PIPE, check=False)
            except FileNotFoundError:
                pass
            except PermissionError:
                likely_invalid_path = True

        if not os.path.exists(svg_path):
            # this is the command for a very old inkscape version (pre 1.0)
            export_args = [binary, "--file", emf_path, "--export-plain-svg", svg_path]

            try:
                try:
                    _ = subprocess.run(
                        export_args,
                        stdout=subprocess.PIPE,
                        creationflags=CREATE_NO_WINDOW,
                        check=False,
                    )
                except ValueError:
                    _ = subprocess.run(export_args, stdout=subprocess.PIPE, check=False)
            except FileNotFoundError:
                pass
            except PermissionError:
                likely_invalid_path = True

        # pylint: enable=subprocess-run-check

        if not os.path.exists(svg_path):
            export_args = [
                "libreoffice",
                "--headless",
                "--convert-to",
                "svg",
                emf_path,
                "--outdir",
                Path(svg_path).parent.as_posix(),
            ]

            try:
                try:
                    _ = subprocess.run(
                        export_args,
                        stdout=subprocess.PIPE,
                        creationflags=CREATE_NO_WINDOW,
                        check=False,
                    )
                except ValueError:
                    _ = subprocess.run(export_args, stdout=subprocess.PIPE, check=False)
                lo_office_export_path = os.path.exists(
                    (Path(svg_path).parent / Path(emf_path).stem).as_posix() + ".svg"
                )
                if os.path.exists(lo_office_export_path) and lo_office_export_path:
                    try:
                        shutil.copy(
                            (Path(svg_path).parent / Path(emf_path).stem).as_posix()
                            + ".svg",
                            svg_path,
                        )
                    except SameFileError:
                        pass
            except FileNotFoundError:
                pass
            except PermissionError:
                likely_invalid_path = True

        if not os.path.exists(svg_path):
            # didn't work
            if likely_invalid_path:
                context.push_warning(
                    "Invalid path to Inkscape executable -- cannot convert EMF content",
                    level=Context.CRITICAL,
                )
            else:
                context.push_warning(
                    "Conversion of EMF content requires a valid path to an Inkscape install setup in the SLYR options",
                    level=Context.CRITICAL,
                )

        else:
            # remove width/height from svg tag, so that the viewbox is correctly re-calculated
            with open(svg_path, "rt", encoding="utf-8") as svg_file:
                doc = QDomDocument()
                doc.setContent("".join(svg_file.readlines()))

                doc.documentElement().removeAttribute("width")
                doc.documentElement().removeAttribute("height")

                out = doc.toString(2)
            with open(svg_path, "wt", encoding="utf-8") as svg_file:
                svg_file.write(out)

    # pylint: enable=too-many-branches, too-many-statements

    @staticmethod
    def write_svg(content: str, symbol_name: str, picture_folder: str):
        """
        Writes a picture binary content to an SVG file
        """
        path = SymbolConverter.symbol_name_to_filename(
            symbol_name, picture_folder, "svg"
        )
        with open(path, "wt", encoding="utf8") as f:
            f.write(content)
        return path

    @staticmethod
    def get_picture_data(picture, fg, bg, trans, context=None):
        """
        Retrieves picture binary content, converting raster colors if necessary
        """
        if issubclass(picture.__class__, StdPicture):
            picture = picture.picture
        fg_color = ColorConverter.color_to_qcolor(fg) if fg else None
        bg_color = ColorConverter.color_to_qcolor(bg) if bg else None
        trans_color = (
            ColorConverter.color_to_qcolor(trans)
            if trans and not trans.is_null
            else None
        )
        if picture is None or picture.content is None:
            if context:
                context.push_warning(
                    "Picture data is missing or corrupt", level=Context.CRITICAL
                )
            return None
        else:
            return PictureUtils.set_colors(
                picture.content, fg_color, bg_color, trans_color
            )

    @staticmethod
    def write_picture(
        picture, symbol_name: str, picture_folder: str, fg, bg, trans, context=None
    ):
        """
        Writes a picture binary content to a file, converting raster colors if necessary
        """
        new_content = SymbolConverter.get_picture_data(
            picture, fg, bg, trans, context=context
        )

        path = SymbolConverter.symbol_name_to_filename(
            symbol_name, picture_folder, "png"
        )
        PictureUtils.to_png(new_content, path)
        return path

    @staticmethod
    def append_PictureFillSymbolLayer(
        symbol,  # pylint: disable=too-many-statements,too-many-branches,too-many-locals
        layer: PictureFillSymbol,
        context: Context,
    ):
        """
        Appends a PictureFillSymbolLayer to a symbol
        """

        if layer.separation_x or layer.separation_y:
            context.push_warning(
                "Picture fill separation X or Y is not supported by QGIS"
            )

        picture = layer.picture
        if issubclass(picture.__class__, StdPicture):
            picture = picture.picture

        if issubclass(picture.__class__, EmfPicture):
            path = SymbolConverter.symbol_name_to_filename(
                context.symbol_name, context.get_picture_store_folder(), "emf"
            )
            with open(path, "wb") as f:
                f.write(picture.content)

            svg_path = SymbolConverter.symbol_name_to_filename(
                context.symbol_name, context.get_picture_store_folder(), "svg"
            )
            SymbolConverter.emf_to_svg(
                path, svg_path, inkscape_path=context.inkscape_path, context=context
            )
            # no longer need the emf file
            try:
                os.remove(path)
            except PermissionError:
                pass

            if not os.path.exists(svg_path):
                context.push_warning(
                    "Conversion of EMF picture failed", level=Context.CRITICAL
                )

            width_in_in_points = (
                layer.scale_x * QSvgRenderer(svg_path).viewBoxF().width()
            )

            if context.embed_svgs() and os.path.exists(svg_path):
                with open(svg_path, "rb") as svg_file:
                    svg_content = base64.b64encode(svg_file.read()).decode("UTF-8")
                # no longer need the svg file
                try:
                    os.remove(svg_path)
                except PermissionError:
                    pass
                svg_path = "base64:{}".format(svg_content)

            out = QgsSVGFillSymbolLayer(
                svg_path,
                context.convert_size(width_in_in_points),
                ConversionUtils.convert_angle(layer.angle),
            )
            out.setPatternWidthUnit(context.units)
            outline = QgsLineSymbol()
            outline.changeSymbolLayer(
                0, QgsSimpleLineSymbolLayer(penStyle=Qt.PenStyle.NoPen)
            )
            out.setSubSymbol(outline)
        else:
            # use raster fill
            picture_data = SymbolConverter.get_picture_data(
                picture,
                layer.color_foreground,
                layer.color_background,
                None,
                context=context,
            )
            if picture_data:
                image_base64 = base64.b64encode(picture_data).decode("UTF-8")
                image_path = "base64:{}".format(image_base64)
            else:
                image_path = ""

            out = QgsRasterFillSymbolLayer(image_path)

            if picture and picture.content:
                # convert to points, so that print layouts work nicely. It's a better match for Arc anyway
                width_in_pixels = layer.scale_x * PictureUtils.width_pixels(
                    picture.content
                )
                width_in_in_points = width_in_pixels / 96 * 72

                out.setWidth(context.convert_size(width_in_in_points))
                out.setWidthUnit(context.units)

            out.setAngle(ConversionUtils.convert_angle(layer.angle))

        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)
        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )
        if isinstance(layer.outline, MultiLayerLineSymbol):
            # get all layers from outline
            SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                symbol, layer.outline, context
            )
        elif layer.outline:
            SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                symbol, layer.outline, context
            )

    @staticmethod
    def append_TextureFillSymbolLayer(
        symbol, layer: TextureFillSymbol, context: Context
    ):
        """
        Appends a TextureFillSymbol to a symbol
        """

        if not layer.texture:
            return

        picture = layer.texture.picture
        if not picture:
            return

        context.push_warning("3D texture fill was converted to a 2d fill")

        # use raster fill
        picture_data = SymbolConverter.get_picture_data(
            picture, None, layer.color, layer.transparency_color, context=context
        )
        image_base64 = base64.b64encode(picture_data).decode("UTF-8")
        image_path = "base64:{}".format(image_base64)

        out = QgsRasterFillSymbolLayer(image_path)

        # convert to points, so that print layouts work nicely. It's a better match for Arc anyway
        width_in_pixels = layer.size * PictureUtils.width_pixels(picture.content)
        width_in_in_points = width_in_pixels / 96 * 72 / 2.5

        out.setWidth(context.convert_size(width_in_in_points))
        out.setWidthUnit(context.units)

        out.setAngle(ConversionUtils.convert_angle(layer.angle))

        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)
        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )
        if isinstance(layer.outline, MultiLayerLineSymbol):
            # get all layers from outline
            SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                symbol, layer.outline, context
            )
        elif layer.outline:
            SymbolConverter.append_SymbolLayer_to_QgsSymbolLayer(
                symbol, layer.outline, context
            )

    @staticmethod
    def append_SimpleLineSymbolLayer(symbol, layer, context: Context):
        """
        Appends a SimpleLineSymbolLayer to a symbol
        """
        color = ColorConverter.color_to_qcolor(layer.color)
        out = QgsSimpleLineSymbolLayer(color)
        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)

        out.setWidth(
            context.convert_size(context.fix_line_width(layer.width))
        )  # sometimes lines have negative width?
        out.setWidthUnit(context.units)

        # for arcgis, a pen width of 0 is not drawn, yet in QGIS it's a "hairline" size
        out.setPenStyle(
            ConversionUtils.symbol_pen_to_qpenstyle(layer.line_type)
            if layer.width > 0
            else Qt.PenStyle.NoPen
        )
        # better match for ArcGIS rendering
        out.setPenCapStyle(Qt.PenCapStyle.RoundCap)
        out.setPenJoinStyle(Qt.PenJoinStyle.RoundJoin)

        # better matching of null stroke color to QGIS symbology
        if out.color().alpha() == 0:
            out.setPenStyle(Qt.PenStyle.NoPen)

        if (
            context.apply_conversion_tweaks
            and out.color().alpha() == 0
            or out.penStyle() == Qt.PenStyle.NoPen
        ):
            # avoid invisible layers
            return

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

    @staticmethod
    def circular_mean(angles):
        """
        Calculates average angle
        """
        x = y = 0.0
        for angle in angles:
            x += math.cos(math.radians(angle))
            y += math.sin(math.radians(angle))

        mean = (math.degrees(math.atan2(y, x)) + 360) % 360
        return mean

    @staticmethod
    def append_SimpleLine3DSymbolLayer(
        symbol, layer: SimpleLine3DSymbol, context: Context
    ):
        """
        Appends a SimpleLine3DSymbol to a symbol
        """

        context.push_warning("3D line was converted to a simple line")
        color = ColorConverter.color_to_qcolor(layer.color)
        out = QgsSimpleLineSymbolLayer(color)
        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)
        out.setWidth(
            context.convert_size(context.fix_line_width(layer.width))
        )  # sometimes lines have negative width?
        out.setWidthUnit(context.units)
        out.setPenCapStyle(Qt.PenCapStyle.RoundCap)
        out.setPenJoinStyle(Qt.PenJoinStyle.RoundJoin)

        # better matching of null stroke color to QGIS symbology
        if out.color().alpha() == 0:
            out.setPenStyle(Qt.PenStyle.NoPen)

        if (
            context.apply_conversion_tweaks
            and out.color().alpha() == 0
            or out.penStyle() == Qt.PenStyle.NoPen
        ):
            # avoid invisible layers
            return

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

    @staticmethod
    def apply_template_to_LineSymbolLayer_custom_dash(
        template, layer, context: Context
    ):
        """
        Applies a line template to a QgsSimpleLineSymbolLayer custom dash pattern
        """
        if not template.pattern_parts:
            return

        interval = context.convert_size(template.pattern_interval)

        dash_vector = []
        for part in template.pattern_parts:
            if part[0] == 0:
                # QGIS skips drawing a 0 part, so fake it
                dash_vector.append(0.000001)
            else:
                dash_vector.append(part[0] * interval)

            if part[1] == 0:
                # QGIS skips drawing a 0 part, so fake it
                dash_vector.append(0.000001)
            else:
                dash_vector.append(part[1] * interval)

        layer.setCustomDashVector(dash_vector)
        layer.setUseCustomDashPattern(True)
        layer.setCustomDashPatternUnit(context.units)

    @staticmethod
    def append_CartographicLineSymbolLayer(
        symbol, layer: CartographicLineSymbol, context: Context
    ):
        """
        Appends a CartographicLineSymbolLayer to a symbol
        """

        color = ColorConverter.color_to_qcolor(layer.color)
        out = QgsSimpleLineSymbolLayer(color)
        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)
        out.setWidth(
            context.convert_size(context.fix_line_width(layer.width))
        )  # sometimes lines have negative width?
        out.setWidthUnit(context.units)
        out.setPenJoinStyle(ConversionUtils.symbol_pen_to_qpenjoinstyle(layer.join))
        out.setPenCapStyle(ConversionUtils.symbol_pen_to_qpencapstyle(layer.cap))
        if layer.template is not None:
            SymbolConverter.apply_template_to_LineSymbolLayer_custom_dash(
                layer.template, out, context
            )

        # better matching of null stroke color to QGIS symbology
        if out.color().alpha() == 0:
            out.setPenStyle(Qt.PenStyle.NoPen)
        if layer.width <= 0:
            # for arcgis, a pen width of 0 is not drawn, yet in QGIS it's a "hairline" size
            out.setPenStyle(Qt.PenStyle.NoPen)

        # offset direction is flipped in arcgis world
        out.setOffset(-context.convert_size(layer.offset))
        out.setOffsetUnit(context.units)

        if (
            context.apply_conversion_tweaks
            and out.color().alpha() == 0
            or out.penStyle() == Qt.PenStyle.NoPen
        ):
            # avoid invisible layers
            return

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

        if layer.decoration is not None:
            DecorationConverter.append_decorations(
                symbol,
                layer.decoration,
                context,
                enabled=layer.enabled,
                locked=layer.locked,
            )

    @staticmethod
    def append_PictureLineSymbolLayer(
        symbol, layer: PictureLineSymbol, context: Context
    ):
        """
        Appends a PictureLineSymbol to a symbol
        """

        context.push_warning("Converting Picture Line Symbols not implemented")

        color = ColorConverter.color_to_qcolor(layer.color)
        out = QgsSimpleLineSymbolLayer(color)
        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)

        out.setWidth(
            context.convert_size(context.fix_line_width(layer.width))
        )  # sometimes lines have negative width?
        out.setWidthUnit(context.units)

        # for arcgis, a pen width of 0 is not drawn, yet in QGIS it's a "hairline" size
        if layer.width == 0:
            out.setPenStyle(Qt.PenStyle.NoPen)

        # better match for ArcGIS rendering
        out.setPenCapStyle(Qt.PenCapStyle.RoundCap)
        out.setPenJoinStyle(Qt.PenJoinStyle.RoundJoin)

        # better matching of null stroke color to QGIS symbology
        if out.color().alpha() == 0:
            out.setPenStyle(Qt.PenStyle.NoPen)

        if (
            context.apply_conversion_tweaks
            and out.color().alpha() == 0
            or out.penStyle() == Qt.PenStyle.NoPen
        ):
            # avoid invisible layers
            return

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

    # pylint: disable=too-many-statements,too-many-branches,too-many-locals,too-many-nested-blocks
    @staticmethod
    def append_TemplatedLineSymbolLayer(
        symbol, layer: Union[MarkerLineSymbol, HashLineSymbol], context: Context
    ):
        """
        Appends a MarkerLineSymbolLayer or HashLineSymbol to a symbol
        """

        template = layer.template

        # first work out total length of pattern
        current_length = 0
        for t in template.pattern_parts:
            current_length += t[0]
            current_length += t[1]

        total_length = current_length * template.pattern_interval

        if isinstance(layer, MarkerLineSymbol):
            sub_symbol = SymbolConverter.Symbol_to_QgsSymbol(
                layer.pattern_marker, context
            )

            # freaking weird logic Arc... !!?!?!
            if isinstance(layer.pattern_marker, MultiLayerSymbol):
                first_angle = ConversionUtils.convert_angle(
                    layer.pattern_marker.layers[0].angle
                )
                for layer_idx in range(sub_symbol.symbolLayerCount()):
                    sub_symbol_layer = sub_symbol.symbolLayer(layer_idx)

                    original_layer_index = (
                        context.final_symbol_layer_output_to_input_index_map[
                            sub_symbol_layer
                        ]
                    )

                    original_angle = ConversionUtils.convert_angle(
                        layer.pattern_marker.layers[original_layer_index].angle
                    )

                    if layer_idx == 0:
                        sub_symbol_layer.setAngle(
                            sub_symbol_layer.angle() - original_angle
                        )
                    else:
                        sub_symbol_layer.setAngle(original_angle - first_angle - 90)
            else:
                sub_symbol.setAngle(90)

        elif isinstance(layer, HashLineSymbol):
            sub_symbol = SymbolConverter.Symbol_to_QgsSymbol(layer.line, context)

        if (
            len(template.pattern_parts) == 1  # pylint: disable=too-many-nested-blocks
            and template.pattern_parts[0][1] == 0
        ):
            # special case! (Not described anywhere in ArcMap docs!!)
            # actually means "center of line segment"
            start_symbol = sub_symbol.clone()
            if isinstance(layer, MarkerLineSymbol):
                line = QgsMarkerLineSymbolLayer(True)
                # start_symbol.setAngle(start_symbol.angle() - 90)
            else:
                line = QgsHashedLineSymbolLayer(True)
                line.setHashLength(layer.width)
                line.setHashLengthUnit(QgsUnitTypes.RenderUnit.RenderPoints)
                line.setHashAngle(ConversionUtils.convert_angle(layer.angle - 90))

            line.setSubSymbol(start_symbol)
            line.setOffset(-context.convert_size(layer.offset))
            line.setOffsetUnit(context.units)

            if hasattr(QgsTemplatedLineSymbolLayerBase, "SegmentCenter"):
                line.setPlacement(QgsMarkerLineSymbolLayer.Placement.SegmentCenter)
            else:
                line.setPlacement(QgsMarkerLineSymbolLayer.Placement.CentralPoint)

            line.setEnabled(layer.enabled)
            line.setLocked(layer.locked)
            if layer.symbol_level != 0xFFFFFFFF:
                line.setRenderingPass(layer.symbol_level)
            symbol.appendSymbolLayer(line)
            context.symbol_layer_output_to_input_index_map[line] = (
                context.current_symbol_layer
            )
        else:
            current_offset_from_start = 0
            for t in template.pattern_parts:
                if t[0]:
                    # symbol
                    start_symbol = sub_symbol.clone()
                    if isinstance(layer, MarkerLineSymbol):
                        line = QgsMarkerLineSymbolLayer(True)
                        # start_symbol.setAngle(start_symbol.angle() - 90)
                    else:
                        line = QgsHashedLineSymbolLayer(True)
                        line.setHashLength(layer.width)
                        line.setHashLengthUnit(QgsUnitTypes.RenderUnit.RenderPoints)
                        line.setHashAngle(
                            ConversionUtils.convert_angle(layer.angle - 90)
                        )

                    line.setSubSymbol(start_symbol)
                    line.setOffset(-context.convert_size(layer.offset))
                    line.setOffsetUnit(context.units)
                    line.setInterval(context.convert_size(total_length))
                    line.setIntervalUnit(context.units)
                    line.setOffsetAlongLine(
                        context.convert_size(
                            current_offset_from_start + template.pattern_interval / 2
                        )
                    )
                    line.setOffsetAlongLineUnit(context.units)

                    line.setEnabled(layer.enabled)
                    line.setLocked(layer.locked)
                    if layer.symbol_level != 0xFFFFFFFF:
                        line.setRenderingPass(layer.symbol_level)

                    symbol.appendSymbolLayer(line)
                    context.symbol_layer_output_to_input_index_map[line] = (
                        context.current_symbol_layer
                    )

                    current_offset_from_start += template.pattern_interval * t[0]

                if t[1]:
                    # space
                    current_offset_from_start += template.pattern_interval * t[1]

        if layer.decoration is not None:
            DecorationConverter.append_decorations(
                symbol,
                layer.decoration,
                context,
                enabled=layer.enabled,
                locked=layer.locked,
            )

    # pylint: enable=too-many-statements,too-many-branches,too-many-locals,too-many-nested-blocks

    @staticmethod
    def append_Marker3DSymbolLayer(symbol, layer: Marker3DSymbol, context: Context):
        """
        Appends a Marker3DSymbol to a symbol
        """

        context.push_warning("3D marker was converted to a simple marker")
        picture = layer.picture
        if issubclass(picture.__class__, StdPicture):
            picture = picture.picture

        image = PictureUtils.colorize_picture(picture, layer.color)
        png_data = QBuffer()
        image.save(png_data, "png")
        picture_data = png_data.data()
        image_base64 = base64.b64encode(picture_data).decode("UTF-8")
        image_path = "base64:{}".format(image_base64)

        out = QgsRasterMarkerSymbolLayer(image_path, context.convert_size(layer.size_z))
        out.setSizeUnit(context.units)
        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)
        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

    @staticmethod
    def append_SimpleMarker3DSymbolLayer(
        symbol, layer: SimpleMarker3DSymbol, context: Context
    ):
        """
        Appends a SimpleMarker3DSymbol to a symbol
        """

        context.push_warning("3D simple marker was converted to a simple marker")

        marker_type = SymbolConverter.marker_3d_type_to_qgis_type(layer.type)

        # convert size to approximate 2d size, based on ArcMap rendering
        out_size = max(1.5, 6 * math.log(layer.size_z) - 2.5)

        out = QgsSimpleMarkerSymbolLayer(marker_type, context.convert_size(out_size))
        out.setSizeUnit(context.units)

        color = ColorConverter.color_to_qcolor(layer.color)
        out.setColor(color)

        angle = ConversionUtils.convert_angle(layer.z_rotation)
        out.setAngle(angle)

        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)

        out.setOffset(
            ConversionUtils.adjust_offset_for_rotation(
                QPointF(
                    context.convert_size(layer.x_offset),
                    -context.convert_size(layer.y_offset),
                ),
                layer.z_rotation,
            )
        )
        out.setOffsetUnit(context.units)

        out.setStrokeStyle(Qt.PenStyle.NoPen)

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

    @staticmethod
    def marker_type_to_qgis_type(marker_type):
        """
        Converts simple marker types to corresponding QGIS types
        """
        if marker_type == "circle":
            return QgsSimpleMarkerSymbolLayerBase.Shape.Circle
        elif marker_type == "square":
            return QgsSimpleMarkerSymbolLayerBase.Shape.Square
        elif marker_type == "cross":
            return QgsSimpleMarkerSymbolLayerBase.Shape.Cross
        elif marker_type == "x":
            return QgsSimpleMarkerSymbolLayerBase.Shape.Cross2
        elif marker_type == "diamond":
            return QgsSimpleMarkerSymbolLayerBase.Shape.Diamond
        else:
            raise NotImplementedException(
                "Marker type {} not implemented".format(marker_type)
            )

    @staticmethod
    def marker_3d_type_to_qgis_type(symbol_type):
        """
        Converts simple 3d marker types to corresponding QGIS types
        """
        if symbol_type == SimpleMarker3DSymbol.TETRAHEDRON:
            return QgsSimpleMarkerSymbolLayerBase.Shape.Triangle
        elif symbol_type == SimpleMarker3DSymbol.CUBE:
            return QgsSimpleMarkerSymbolLayerBase.Shape.Square
        elif symbol_type in (
            SimpleMarker3DSymbol.CONE,
            SimpleMarker3DSymbol.CYLINDER,
            SimpleMarker3DSymbol.SPHERE,
            SimpleMarker3DSymbol.SPHERE_FRAME,
        ):
            return QgsSimpleMarkerSymbolLayerBase.Shape.Circle
        elif symbol_type == SimpleMarker3DSymbol.DIAMOND:
            return QgsSimpleMarkerSymbolLayerBase.Shape.Diamond
        else:
            raise NotImplementedException(
                "Marker type {} not implemented".format(symbol_type)
            )

    @staticmethod
    def append_SimpleMarkerSymbolLayer(
        symbol, layer: SimpleMarkerSymbol, context: Context
    ):
        """
        Appends a SimpleMarkerSymbolLayer to a symbol
        """

        marker_type = SymbolConverter.marker_type_to_qgis_type(layer.type)
        out = QgsSimpleMarkerSymbolLayer(marker_type, context.convert_size(layer.size))

        out.setSizeUnit(context.units)

        color = ColorConverter.color_to_qcolor(layer.color)

        stroke_only_symbol = layer.type not in ("circle", "square", "diamond")
        if not stroke_only_symbol:
            out.setColor(color)
        else:
            out.setStrokeColor(color)

        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)

        out.setOffset(
            QPointF(
                context.convert_size(layer.x_offset),
                -context.convert_size(layer.y_offset),
            )
        )
        out.setOffsetUnit(context.units)

        if layer.outline_enabled:
            outline_color = ColorConverter.color_to_qcolor(layer.outline_color)
            if not stroke_only_symbol:
                out.setStrokeColor(outline_color)
                if not layer.color.is_null:
                    # Better match to how ESRI renders this if we divide the outline width by 2,
                    # because ESRI renders the stroke below the symbol. Maybe we should split this
                    # into two layers?
                    out.setStrokeWidth(context.convert_size(layer.outline_width / 2))
                else:
                    out.setStrokeWidth(context.convert_size(layer.outline_width))
                out.setStrokeWidthUnit(context.units)
                if layer.outline_width <= 0:
                    out.setStrokeStyle(Qt.PenStyle.NoPen)
            else:
                # for stroke-only symbols, we need to add the outline as an additional
                # symbol layer
                outline_layer = QgsSimpleMarkerSymbolLayer(
                    marker_type, context.convert_size(layer.size)
                )
                outline_layer.setSizeUnit(context.units)
                outline_layer.setStrokeColor(outline_color)
                outline_layer.setStrokeWidth(
                    context.convert_size(context.fix_line_width(layer.outline_width))
                )
                outline_layer.setStrokeWidthUnit(context.units)
                symbol.appendSymbolLayer(outline_layer)
                context.symbol_layer_output_to_input_index_map[outline_layer] = (
                    context.current_symbol_layer
                )
        elif not stroke_only_symbol:
            out.setStrokeStyle(Qt.PenStyle.NoPen)

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

    @staticmethod
    def convert_marker_component_to_svg_path(
        geom: QgsGeometry, overall_bounds: QgsRectangle, context: Context
    ) -> str:
        """
        Converts a marker graphic to a SVG symbol layer
        """

        transform = QTransform.fromScale(1, -1)
        geom.transform(transform)

        gen = QSvgGenerator()
        svg_path = SymbolConverter.symbol_name_to_filename(
            context.symbol_name, context.get_picture_store_folder(), "svg"
        )
        gen.setFileName(svg_path)
        gen.setViewBox(overall_bounds.toRectF())

        painter = QPainter(gen)
        painter.setBrush(QBrush(QColor(255, 0, 0)))
        painter.setPen(Qt.PenStyle.NoPen)

        geom.constGet().draw(painter)
        painter.end()

        with open(svg_path, "r", encoding="utf-8") as f:
            t = f.read()

        t = t.replace("#ff0000", "param(fill)")
        t = t.replace('fill-opacity="1"', 'fill-opacity="param(fill-opacity)"')
        t = t.replace(
            'stroke="none"',
            'stroke="param(outline)" stroke-opacity="param(outline-opacity) 1" stroke-width="param(outline-width)"',
        )

        if context.embed_svgs():
            svg_content = base64.b64encode(t.encode("UTF-8")).decode("UTF-8")
            # no longer need the svg file
            try:
                os.remove(svg_path)
            except PermissionError:
                pass
            svg_path = "base64:{}".format(svg_content)
        else:
            with open(svg_path, "wt", encoding="utf-8") as f:
                f.write(t)

        return svg_path

    @staticmethod
    def append_ArrowMarkerSymbolLayer(
        symbol, layer: ArrowMarkerSymbol, context: Context
    ):
        """
        Appends a ArrowMarkerSymbolLayer to a symbol
        """

        out = QgsEllipseSymbolLayer()
        out.setSymbolName("triangle")
        out.setStrokeStyle(Qt.PenStyle.NoPen)

        out.setSymbolHeight(context.convert_size(layer.size))
        out.setSymbolHeightUnit(context.units)
        out.setSymbolWidth(context.convert_size(layer.width))
        out.setSymbolWidthUnit(context.units)

        color = ColorConverter.color_to_qcolor(layer.color)
        out.setColor(color)
        out.setStrokeColor(
            color
        )  # why not, makes the symbol a bit easier to modify in qgis

        out.setOffset(
            ConversionUtils.adjust_offset_for_rotation(
                QPointF(
                    context.convert_size(layer.x_offset),
                    -context.convert_size(layer.y_offset),
                ),
                layer.angle,
            )
        )
        out.setOffsetUnit(context.units)

        angle = 90 - layer.angle
        if angle <= -180:
            angle += 360
        if angle > 180:
            angle -= 360
        out.setAngle(angle)

        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

    ESRI_FONTS_TO_QGIS_MARKERS = {
        "ESRI Default Marker": {
            32: None,
            33: {"shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle},
            34: {"shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square},
            35: {"shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle},
            36: {"shape": QgsSimpleMarkerSymbolLayerBase.Shape.Pentagon},
            37: {"shape": QgsSimpleMarkerSymbolLayerBase.Shape.Hexagon},
            40: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
            },
            41: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "outline_only": True,
            },
            42: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "outline_only": True,
            },
            43: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Pentagon,
                "outline_only": True,
            },
            44: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Hexagon,
                "outline_only": True,
            },
            46: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
            },
            47: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "outline_only": True,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
            },
            48: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "outline_only": True,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
            },
            49: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Pentagon,
                "outline_only": True,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
            },
            50: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Hexagon,
                "outline_only": True,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
            },
            53: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
            },
            54: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "outline_only": True,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
            },
            55: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "outline_only": True,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
            },
            56: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Pentagon,
                "outline_only": True,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
            },
            57: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Hexagon,
                "outline_only": True,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
            },
            60: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "overlay": QgsSimpleMarkerSymbolLayerBase.Shape.SemiCircle,
                "overlay_angle": 90,
            },
            61: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Line,
            },
            62: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
            },
            63: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "overlay_angle": 45,
            },
            65: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 8.129480 / 12.129480,
                "outline_only": True,
                "overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "overlay_size_factor": 13.249480 / 8.129480,
            },
            66: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.8548989734102369,
                "outline_only": True,
                "overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "overlay_size_factor": 13.249480 / 8.129480,
                "overlay_angle": 45,
            },
            68: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross2,
                "outline_only": True,
                "shape_size_factor": 0.8919171482489955,
                "stroke_size_factor": 3.581800012565442,
            },
            69: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "outline_only": True,
                "shape_size_factor": 0.8919171482489955,
                "stroke_size_factor": 4.88062371717723,
            },
            72: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "stroke_size_factor": 2.813333333333333,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "overlay_size_factor": 1.3654742518282246,
            },
            73: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "outline_only": True,
                "stroke_size_factor": 1.5143456548019383,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "overlay_size_factor": 0.26396286832045446,
                "overlay_y_offset_factor": 1.3703703703703705,
            },
            74: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "outline_only": True,
                "shape_size_factor": 0.8895253547555213,
                "stroke_size_factor": 1.9410120363760561,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "overlay_size_factor": 1.6658045385884221,
            },
            75: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Pentagon,
                "outline_only": True,
                "shape_size_factor": 1.0098932629689819,
                "stroke_size_factor": 1.9410120363760561,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Pentagon,
                "overlay_size_factor": 0.3228621997955166,
                "overlay_y_offset": 0.14955468922886098,
            },
            76: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Hexagon,
                "outline_only": True,
                "shape_size_factor": 1.0098932629689819,
                "stroke_size_factor": 1.9410120363760561,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Hexagon,
                "overlay_size_factor": 0.3228621997955166,
                "overlay_angle": 90,
            },
            80: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 0.9571292527171059,
                "stroke_size_factor": 2.90101098080052,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "overlay_size_factor": 2.1608275432262554,
                "overlay_angle": 0,
            },
            82: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 0.9736180059208173,
                "stroke_size_factor": 1.7810108595221255,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "overlay_size_factor": 1.7135893730603813,
                "central_overlay_outline_only": True,
                "overlay_angle": 0,
            },
            83: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "outline_only": True,
                "shape_size_factor": 0.9967022604060128,
                "stroke_size_factor": 1.087677451111691,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Cross2,
                "overlay_size_factor": 0.8836814401705497,
                "overlay_angle": 0,
            },
            85: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 0.9736180059208173,
                "stroke_size_factor": 1.7810108595221255,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "overlay_size_factor": 0.8552535423790505,
                "central_overlay_outline_only": True,
                "overlay_angle": 0,
            },
            86: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "outline_only": True,
                "shape_size_factor": 1.0445196446967753,
                "stroke_size_factor": 1.7810108595221255,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "overlay_size_factor": 0.4931118372114387,
                "overlay_y_offset_factor": 1.0354584362120463,
                "overlay_angle": 0,
            },
            87: {
                "ellipse_marker_type": "diamond",
                "outline_only": True,
                "width_factor": 2.620000 / 13.990840,
                "height_factor": 4.590840 / 13.990840,
            },
            88: {
                "ellipse_marker_type": "diamond",
                "width_factor": 2.620000 / 13.990840,
                "height_factor": 4.590840 / 13.990840,
            },
            89: {
                "ellipse_marker_type": "diamond",
                "outline_only": True,
                "width_factor": 2.260000 / 13.990840,
                "height_factor": 3.930840 / 13.990840,
                "stroke_scale": 0.991667 / 0.791667,
            },
            90: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 0.9736180059208173,
                "stroke_size_factor": 1.7810108595221255,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "overlay_size_factor": 0.4780225994961215,
                "overlay_angle": 0,
            },
            91: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 1.0065955123282395,
                "stroke_size_factor": 0.7676774164607211,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "overlay_size_factor": 0.3663622404027745,
                "overlay_angle": 0,
            },
            92: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 1.0065955123282395,
                "stroke_size_factor": 0.7676774164607211,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "overlay_size_factor": 0.894485560438875,
                "overlay_angle": 0,
            },
            93: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 1.0065955123282395,
                "stroke_size_factor": 0.7676774164607211,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "overlay_size_factor": 0.894485560438875,
                "overlay_stroke_factor": 1.55,
                "central_overlay_outline_only": True,
                "overlay_angle": 0,
            },
            96: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "outline_only": True,
                "shape_size_factor": 1.0065955123282395,
                "stroke_size_factor": 0.7676774164607211,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "overlay_size_factor": 0.2562107512450968,
                "overlay_y_offset_factor": 1.2943551873662755,
                "overlay_angle": 0,
            },
            94: {"shape": QgsSimpleMarkerSymbolLayerBase.Shape.Star},
            95: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "outline_only": True,
            },
            169: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 1.0296797668134352,
                "stroke_size_factor": 0.927677433786206,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "overlay_size_factor": 0.7782984300309329,
                "overlay_stroke_factor": 5.229436512101896,
                "central_overlay_outline_only": True,
                "overlay_angle": 0,
            },
            170: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 1.0296797668134352,
                "stroke_size_factor": 0.927677433786206,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "overlay_size_factor": 0.7782984300309329,
                "overlay_stroke_factor": 5.229436512101896,
                "central_overlay_outline_only": True,
                "overlay_angle": 45,
            },
            171: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 1.0296797668134352,
            },
            172: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.9165104847042012,
            },
            183: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.9451800361718738,
            },
            185: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 0.9451800361718738,
            },
            196: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.9451800361718738,
            },
            199: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.5770026383764989,
            },
            200: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.6418863601191265,
            },
            201: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.9390556014511079,
            },
            203: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "outline_only": True,
                "shape_size_factor": 0.9006464914345718,
                "stroke_size_factor": 4.880623717177232,
            },
            204: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "outline_only": True,
                "shape_size_factor": 0.9006464914345718,
                "stroke_size_factor": 4.541800142061113,
            },
            205: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "outline_only": True,
                "shape_size_factor": 0.9268345209913009,
                "stroke_size_factor": 3.920623587681562,
            },
            206: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "outline_only": True,
                "shape_size_factor": 0.9442932073624539,
                "stroke_size_factor": 3.2994470333020107,
            },
            207: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "outline_only": True,
                "shape_size_factor": 0.963497762370722,
                "stroke_size_factor": 2.6217998830697717,
            },
            208: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "outline_only": True,
                "shape_size_factor": 0.9757188428305291,
                "stroke_size_factor": 2.17003511624828,
            },
            209: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "outline_only": True,
                "shape_size_factor": 0.9931775292016818,
                "stroke_size_factor": 1.7182703494267881,
            },
            210: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "outline_only": True,
                "shape_size_factor": 1.0071444782986039,
                "stroke_size_factor": 1.2665055826052964,
            },
            211: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "outline_only": True,
                "shape_size_factor": 1.019365558758411,
                "stroke_size_factor": 0.8712114116364911,
            },
        },
        "ESRI ArcGIS TDN": {
            35: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.12932914989379118,
            },
            39: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Line,
                "angle": 90,
                "shape_size_factor": 0.7439879243257865,
                "outline_only": True,
                "stroke_size_factor": 2.653333333333333,
            },
            50: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.33152505554488476,
                "outline_only": True,
                "stroke_size_factor": 2.706666666666666,
            },
            52: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "shape_size_factor": 0.4115613882341978,
                "outline_only": True,
                "stroke_size_factor": 2.6799999999999993,
            },
            53: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 0.326578429600613,
                "outline_only": True,
                "stroke_size_factor": 1.7733333333333332,
                "vertical_offset_factor": 1.2457202154021212,
            },
            55: {
                "ellipse_marker_type": "rectangle",
                "outline_only": True,
                "width_factor": 0.17360565193140656,
                "height_factor": 0.09988725061195887,
                "stroke_scale": 0.8392987532397534,
            },
            71: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.4741183593831859,
            },
            72: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.6349181134602188,
                "outline_only": True,
                "stroke_size_factor": 3.3733333333333326,
            },
            86: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.6122842583476866,
            },
            87: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.1909582764126922,
                "height_factor": 0.11346755240505034,
            },
            88: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.31341791819885095,
            },
            89: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.35131156670274644,
                "outline_only": True,
                "stroke_size_factor": 1.16,
            },
            93: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.04784723605384667,
            },
            95: {
                "ellipse_marker_type": "circle",
                "width_factor": 0.17360565193140656,
                "height_factor": 0.06970876571721113,
                "outline_only": True,
                "stroke_scale": 1.2126321250821663,
            },
            96: {
                "ellipse_marker_type": "circle",
                "width_factor": 0.2022752054222263,
                "height_factor": 0.09611493009780375,
            },
            97: {
                "ellipse_marker_type": "circle",
                "width_factor": 0.17134226612949977,
                "height_factor": 0.08027123146944817,
            },
        },
        "ESRI ArcPad": {
            33: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.9344231669332914,
            },
            34: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.9344231669332914,
            },
            35: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 0.990491198259151,
            },
            37: {
                "ellipse_marker_type": "diamond",
                "width_factor": 0.19685181337401686,
                "height_factor": 0.34262196532114203,
            },
            38: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "shape_size_factor": 0.990491198259151,
            },
        },
        "ESRI Arrowhead": {
            62: {
                "ellipse_marker_type": "triangle",
                "width_factor": 0.3426218270750293,
                "height_factor": 0.2055731791926852,
                "angle": 90,
            },
            63: {
                "ellipse_marker_type": "triangle",
                "width_factor": 0.2180328646217991,
                "height_factor": 0.39245907681960146,
                "angle": 90,
            },
            190: {
                "ellipse_marker_type": "triangle",
                "width_factor": 0.3426218270750293,
                "height_factor": 0.2055731791926852,
                "angle": 90,
            },
            191: {
                "ellipse_marker_type": "triangle",
                "width_factor": 0.2180328646217991,
                "height_factor": 0.39245907681960146,
                "angle": 270,
            },
        },
        "ESRI Business": {
            33: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.97180268508445,
            },
            41: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "shape_size_factor": 1.2205416261711395,
            },
            45: {
                "ellipse_marker_type": "diamond",
                "width_factor": 0.19809839397240805,
                "height_factor": 0.34262299093638626,
            },
            190: {
                "ellipse_marker_type": "circle",
                "width_factor": 0.5045902487976431,
                "height_factor": 0.34262299093638626,
            },
            191: {
                "ellipse_marker_type": "circle",
                "width_factor": 0.4859017210643971,
                "height_factor": 0.3363934820102702,
            },
        },
        "ESRI Geology USGS 95-525": {
            35: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Line,
                "outline_only": True,
                "shape_size_factor": 0.15666864070024933,
                "stroke_size_factor": 1.433351703353058,
            },
        },
        "ESRI Cartography": {
            72: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 0.939134942311759,
                "stroke_size_factor": 2.829715050724875,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "overlay_size_factor": 1.6504965013928692,
                "overlay_stroke_factor": 1.0000002023905774,
                "central_overlay_outline_only": True,
            },
            73: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 0.8986625474008189,
                "stroke_size_factor": 3.9642602704644765,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "overlay_size_factor": 1.5753070239000497,
                "overlay_stroke_factor": 1.0000002023905774,
                "central_overlay_outline_only": True,
            },
            74: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Line,
                "outline_only": True,
                "shape_size_factor": 0.8986625474008189,
                "stroke_size_factor": 3.9642602704644765,
                "cap_style": Qt.PenCapStyle.RoundCap,
            },
            77: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "outline_only": True,
                "shape_size_factor": 0.2106318339148362,
                "stroke_size_factor": 3.0410493984906326,
            },
            78: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.29211369052653874,
            },
            179: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Line,
                "outline_only": True,
                "shape_size_factor": 0.9674656187494172,
                "stroke_size_factor": 1.8697152494067508,
                "cap_style": Qt.PenCapStyle.RoundCap,
            },
            202: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "shape_size_factor": 1.2056605537118097,
            },
        },
        "ESRI Caves 1": {
            33: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 1.1213107904820578,
            },
            39: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 1.0216387202169859,
            },
            42: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 1.1213107904820578,
            },
            114: {
                "ellipse_marker_type": "triangle",
                "width_factor": 0.2616392931934454,
                "height_factor": 0.180655765520134,
            },
            116: {
                "ellipse_marker_type": "triangle",
                "width_factor": 0.17442619546229693,
                "height_factor": 0.2803277186845398,
            },
            212: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 1.0714747553495219,
            },
        },
        "ESRI Caves 2": {
            36: {
                "ellipse_marker_type": "square",
                "width_factor": 0.3768851723381773,
                "height_factor": 0.2566556002178454,
            },
            84: {
                "ellipse_marker_type": "square",
                "width_factor": 0.38622943280937183,
                "height_factor": 0.19436064597110914,
            },
            238: {
                "ellipse_marker_type": "triangle",
                "width_factor": 0.2616392931934454,
                "height_factor": 0.180655765520134,
            },
        },
        "ESRI Caves 3": {
            49: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.5733910903740004,
            },
            53: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.6410581523442068,
            },
            55: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 1.003762106960008,
            },
            60: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 1.003762106960008,
            },
            62: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 1.003762106960008,
            },
            64: {
                "ellipse_marker_type": "square",
                "width_factor": 0.3416168081587184,
                "height_factor": 0.17713463966317866,
            },
            66: {
                "ellipse_marker_type": "square",
                "width_factor": 0.4242796407502108,
                "height_factor": 0.2977548942909622,
            },
            68: {
                "ellipse_marker_type": "square",
                "width_factor": 0.4242796407502108,
                "height_factor": 0.2977548942909622,
            },
        },
        "ESRI Climate & Precipitation": {
            37: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 0.4597061750363063,
            },
            38: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 0.4597061750363063,
                "angle": 180,
            },
            43: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.SemiCircle,
                "shape_size_factor": 0.45347343460731465,
            },
            44: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.SemiCircle,
                "shape_size_factor": 0.45347343460731465,
                "angle": 180,
            },
            86: {
                "ellipse_marker_type": "circle",
                "width_factor": 0.10965477792748986,
                "height_factor": 0.10290679066146569,
            },
            96: {
                "ellipse_marker_type": "triangle",
                "width_factor": 0.37113924836996565,
                "height_factor": 0.198222096765938,
            },
            120: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.SemiCircle,
                "shape_size_factor": 0.45347343460731465,
            },
            197: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.3458338988394936,
                "vertical_offset_factor": 1.575,
                "horizontal_offset_factor": 0.1,
            },
            202: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.3458338988394936,
                "vertical_offset_factor": 1.575,
                "horizontal_offset_factor": 0.1,
            },
            208: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.41331368397890705,
            },
            211: {
                "ellipse_marker_type": "triangle",
                "width_factor": 0.3458342996174679,
                "height_factor": 0.26148446807421605,
            },
            228: {
                "ellipse_marker_type": "triangle",
                "width_factor": 0.3458342996174679,
                "height_factor": 0.12230725119600427,
                "angle": 90,
                "vertical_offset_factor": 0.1,
            },
        },
        "ESRI AMFM Electric": {
            33: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 1.0153634389208162,
            },
            48: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 1.078738211080188,
            },
            54: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.5365451835215594,
            },
            58: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.19944873314152206,
            },
            88: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.38364351448646505,
                "height_factor": 0.19917766074151325,
            },
            93: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 1.086424111820078,
            },
            94: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.2939387713086604,
                "height_factor": 0.22409564370249616,
            },
            95: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.3936107081728878,
                "height_factor": 0.19809796453981388,
            },
            100: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.9973439687729347,
            },
            172: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.4572450103646429,
                "height_factor": 0.19917766074151325,
            },
            205: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.3064912492840521,
                "height_factor": 0.3737697444147432,
            },
            206: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.20308198376704445,
                "height_factor": 0.3874757421168326,
            },
            219: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.5108184602276327,
                "height_factor": 0.2740977143665235,
            },
            221: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.29278693448752136,
                "height_factor": 0.14701628606616177,
            },
            223: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.1868852773324604,
                "height_factor": 0.3675410266408507,
            },
            226: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.21803282355453715,
                "height_factor": 0.32767216951370753,
            },
            227: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.21429511800788795,
                "height_factor": 0.32767216951370753,
            },
            228: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Hexagon,
                "shape_size_factor": 1.2450068618099817,
            },
            229: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.3463607139894933,
                "height_factor": 0.2691147856082161,
            },
            230: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.46098368408673573,
                "height_factor": 0.23049183026629622,
            },
            231: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.3812459657582193,
                "height_factor": 0.18314756242781374,
            },
            232: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.9468841308974395,
            },
            233: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.4746886044244495,
                "height_factor": 0.31521315166147534,
            },
            234: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.4074099045847637,
                "height_factor": 0.27783609810477866,
            },
        },
        "ESRI AMFM Gas": {
            33: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.4535082729934374,
                "height_factor": 0.3550820087886185,
            },
            37: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.9718012654082938,
            },
            39: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.4049177130453956,
            },
            46: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 1.1026220801390003,
                "angle": 90,
                "vertical_offset_factor": -0.05,
            },
            165: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "outline_only": True,
                "shape_size_factor": 0.9742110179012407,
                "stroke_size_factor": 0.9533518026939961,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "overlay_size_factor": 0.8603691048301341,
                "overlay_stroke_factor": 1.0000002023905774,
                "central_overlay_outline_only": True,
            },
            169: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.9718025113089495,
            },
        },
        "ESRI AMFM Sewer": {
            35: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.7350813867593337,
            },
            39: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 0.8285239359236557,
                "vertical_offset_factor": -0.06,
            },
            41: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.032393448070959806,
                "height_factor": 0.25914757132643035,
            },
            46: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.12708198858607309,
                "height_factor": 0.25914757132643035,
            },
            50: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.12708198858607309,
                "height_factor": 0.25914757132643035,
            },
            72: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Diamond,
                "shape_size_factor": 0.7350813867593335,
            },
            73: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.7350813867593335,
            },
            103: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.728851883481712,
            },
            # TODO
            104: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.CrossFill,
                "shape_size_factor": 0.728851883481712,
            },
            108: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.7238682808596149,
            },
            110: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.2579016827187954,
                "height_factor": 0.12708198209276872,
            },
            112: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.317704667158695,
            },
            113: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 0.8409829424788986,
                "angle": 180,
                "vertical_offset_factor": -0.055,
            },
            114: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.2579016827187954,
                "height_factor": 0.15200001779723318,
            },
            115: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.0660327979908027,
                "height_factor": 0.25914757132643035,
            },
            116: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.36131119010204527,
            },
            117: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.7350813867593335,
            },
            118: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.7313436847927607,
            },
            120: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Diamond,
                "shape_size_factor": 0.7313436847927607,
            },
            121: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.7350813867593335,
            },
            124: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.7350813867593335,
            },
            161: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.7350813867593335,
            },
            162: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.2579016827187954,
                "height_factor": 0.15200001779723318,
            },
            164: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.06727869983968576,
                "height_factor": 0.2566557677559839,
            },
            165: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.06727869983968576,
                "height_factor": 0.2566557677559839,
            },
            234: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.4074099045847637,
                "height_factor": 0.27783609810477866,
            },
        },
        "ESRI AMFM Water": {
            34: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 1.0527860539180285,
            },
            37: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "shape_size_factor": 1.2205421269352341,
            },
            38: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 1.2205421269352341,
            },
            51: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.45724597854008653,
                "height_factor": 0.3550820087886185,
            },
            52: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.9780317355888125,
            },
            82: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.3675410454205056,
                "height_factor": 0.29403282131268055,
            },
            83: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.4859011170441234,
            },
            84: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.1,
            },
            86: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.2554098790210293,
                "height_factor": 0.1532459195824564,
                "horizontal_offset_factor": -1,
            },
            88: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.45475417484232045,
                "height_factor": 0.3550820087886185,
            },
            91: {
                "ellipse_marker_type": "rectangle",
                "width_factor": 0.4834099173666311,
                "height_factor": 0.3737705355669668,
            },
            95: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 0.8409829424788986,
                "angle": 0,
                "vertical_offset_factor": -0.055,
            },
            96: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 1.0652450604732717,
                "angle": 270,
                "vertical_offset_factor": -0.055,
            },
            97: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 1.0652450604732717,
                "angle": 90,
                "vertical_offset_factor": -0.055,
            },
            108: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.4859011170441234,
            },
            197: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Star,
                "shape_size_factor": 1.2205421269352341,
            },
            198: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 1.1208701326813666,
                "angle": 270,
                "vertical_offset_factor": -0.055,
            },
            202: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Hexagon,
                "shape_size_factor": 1.0154728186795665,
                "angle": 30,
            },
            205: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.9112477418018482,
            },
            206: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "outline_only": True,
                "shape_size_factor": 0.9854471388522006,
                "stroke_size_factor": 2.309715072769748,
            },
            214: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "outline_only": True,
                "shape_size_factor": 0.9674656187494172,
                "stroke_size_factor": 1.8697152494067508,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "overlay_size_factor": 0.8603691048301341,
                "overlay_stroke_factor": 1.0000002023905774,
            },
            216: {
                "ellipse_marker_type": "triangle",
                "width_factor": 0.19934429582129118,
                "height_factor": 0.44229513375424406,
                "angle": 90,
            },
            222: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 1.096392576861379,
                "vertical_offset_factor": -0.055,
            },
            231: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Diamond,
                "shape_size_factor": 0.9842615178641925,
            },
        },
        "ESRI Fire Incident NFPA": {
            172: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "outline_only": True,
                "shape_size_factor": 1.0861846438215081,
                "stroke_size_factor": 1.3460789941423195,
                "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "overlay_size_factor": 2.965674412261938,
                "overlay_stroke_factor": 1.0000002023905774,
                "central_overlay_outline_only": True,
            },
        },
        "ESRI Geometric Symbols": {
            36: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 1.0583678399557281,
                "vertical_offset_factor": 0,
            },
        },
        "ESRI Commodities": {
            36: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Line,
                "outline_only": True,
                "angle": 90,
                "shape_size_factor": 0.5559962704881921,
                "stroke_size_factor": 2.087897022433597,
            },
            39: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "outline_only": True,
                "shape_size_factor": 0.9607202195975937,
                "stroke_size_factor": 2.087897022433597,
            },
            40: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                "shape_size_factor": 0.9384978866361668,
            },
            41: {
                "ellipse_marker_type": "diamond",
                "outline_only": True,
                "width_factor": 0.39097001543154347,
                "height_factor": 0.23183501818109928,
                "stroke_scale": 0.9253588831024798,
            },
            42: {
                "ellipse_marker_type": "diamond",
                "width_factor": 0.4230689544408402,
                "height_factor": 0.25529193505688796,
            },
            47: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 1.24343767782908,
                "outline_only": True,
                "stroke_size_factor": 1.9449208614717124,
            },
            48: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                "shape_size_factor": 1.24343767782908,
            },
            50: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.9708757925601526,
                "outline_only": True,
                "stroke_size_factor": 1.9449208614717124,
            },
            51: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 0.9384978866361668,
            },
        },
        "ESRI Mil2525C Modifiers": {
            49: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                "shape_size_factor": 1.0184068897245921,
            },
            50: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Line,
                "outline_only": True,
                "angle": 0,
                "shape_size_factor": 0.8347431703560976,
                "stroke_size_factor": 8.920000215999957,
            },
            51: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross2,
                "outline_only": True,
                "shape_size_factor": 0.7880379973828248,
                "stroke_size_factor": 6.941798148202165,
            },
            52: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                "outline_only": True,
                "shape_size_factor": 0.9586966023772483,
                "stroke_size_factor": 6.1017978882564945,
            },
            110: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Cross2,
                "outline_only": True,
                "shape_size_factor": 1.1664571007145672,
                "stroke_size_factor": 2.141796662798331,
            },
            111: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Line,
                "outline_only": True,
                "angle": 45,
                "shape_size_factor": 1.5989969174376801,
                "stroke_size_factor": 2.141795571640884,
            },
            118: {
                "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Line,
                "outline_only": True,
                "angle": 0,
                "shape_size_factor": 1.0796011669938863,
                "stroke_size_factor": 1.1817957636408465,
            },
        },
    }

    if Qgis.QGIS_VERSION_INT >= 31800:
        ESRI_FONTS_TO_QGIS_MARKERS["ESRI Default Marker"].update(
            {
                38: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Octagon,
                },
                39: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.SquareWithCorners,
                },
                45: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Octagon,
                    "outline_only": True,
                },
                51: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Octagon,
                    "outline_only": True,
                    "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                },
                52: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.SquareWithCorners,
                    "outline_only": True,
                    "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                },
                58: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Octagon,
                    "outline_only": True,
                    "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                },
                59: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.SquareWithCorners,
                    "outline_only": True,
                    "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                },
                77: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Octagon,
                    "outline_only": True,
                    "shape_size_factor": 1.0098932629689819,
                    "stroke_size_factor": 1.9410120363760561,
                    "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.Octagon,
                    "overlay_size_factor": 0.3228621997955166,
                },
                78: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.SquareWithCorners,
                    "outline_only": True,
                    "shape_size_factor": 1.0098932629689819,
                    "stroke_size_factor": 1.0876792732278202,
                    "central_overlay": QgsSimpleMarkerSymbolLayerBase.Shape.SquareWithCorners,
                    "overlay_size_factor": 0.3228621997955166,
                },
                106: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.AsteriskFill,
                    "outline_only": True,
                },
                107: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.AsteriskFill,
                },
                173: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.SquareWithCorners,
                    "outline_only": True,
                    "shape_size_factor": 1.093806079589203,
                },
                176: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Octagon,
                    "outline_only": True,
                    "shape_size_factor": 1.0617429232013191,
                },
            }
        )

        ESRI_FONTS_TO_QGIS_MARKERS["ESRI AMFM Electric"].update(
            {
                215: {
                    "shape": QgsSimpleMarkerSymbolLayerBase.Shape.Octagon,
                    "shape_size_factor": 1.036381918330409,
                },
            }
        )

    if Qgis.QGIS_VERSION_INT >= 32700:
        ESRI_FONTS_TO_QGIS_MARKERS["ESRI AMFM Sewer"].update(
            {
                107: {
                    "ellipse_marker_type": "hexagon",
                    "width_factor": 0.299016444,
                    "height_factor": 0.25790167,
                },
            }
        )

    ALIAS_FONTS = {
        "ESRI AMFM Water": {
            38: {"font_name": "ESRI Default Marker", "character": 33},
            84: {"font_name": "ESRI Default Marker", "character": 33},
            108: {"font_name": "ESRI Default Marker", "character": 33},
        },
        "ESRI Geometric Symbols": {
            34: {
                "font_name": "ESRI Default Marker",
                "character": 33,
                "vertical_offset_factor": 0,
            },
            35: {
                "font_name": "ESRI Default Marker",
                "character": 33,
                "vertical_offset_factor": 0,
            },
        },
        "ESRI NIMA VMAP1&2 LN": {
            35: {"font_name": "ESRI NIMA City Graphic PT", "character": 37},
        },
        "ESRI NIMA VMAP1&2 PT": {
            35: {"font_name": "ESRI NIMA City Graphic PT", "character": 37},
        },
        "ESRI SDS 2.00 1": {
            249: {"font_name": "ESRI SDS 1.95 1", "character": 249},
            196: {"font_name": "ESRI SDS 1.95 1", "character": 196},
            172: {"font_name": "ESRI SDS 1.95 1", "character": 172},
            171: {"font_name": "ESRI SDS 1.95 1", "character": 171},
            168: {"font_name": "ESRI SDS 1.95 1", "character": 168},
            167: {"font_name": "ESRI SDS 1.95 1", "character": 167},
            166: {"font_name": "ESRI SDS 1.95 1", "character": 166},
            161: {"font_name": "ESRI SDS 1.95 1", "character": 161},
            125: {"font_name": "ESRI SDS 1.95 1", "character": 125},
            124: {"font_name": "ESRI SDS 1.95 1", "character": 124},
            123: {"font_name": "ESRI SDS 1.95 1", "character": 123},
            121: {"font_name": "ESRI SDS 1.95 1", "character": 121},
            120: {"font_name": "ESRI SDS 1.95 1", "character": 120},
            119: {"font_name": "ESRI SDS 1.95 1", "character": 119},
            118: {"font_name": "ESRI SDS 1.95 1", "character": 118},
            117: {"font_name": "ESRI SDS 1.95 1", "character": 117},
            105: {"font_name": "ESRI SDS 1.95 1", "character": 105},
            104: {"font_name": "ESRI SDS 1.95 1", "character": 104},
            62: {"font_name": "ESRI SDS 1.95 1", "character": 62},
            61: {"font_name": "ESRI SDS 1.95 1", "character": 61},
        },
        "ESRI SDS 2.00 2": {
            220: {"font_name": "ESRI SDS 1.95 2", "character": 220},
            210: {"font_name": "ESRI SDS 1.95 2", "character": 220},
            209: {"font_name": "ESRI SDS 1.95 2", "character": 209},
            206: {"font_name": "ESRI SDS 1.95 2", "character": 206},
            205: {"font_name": "ESRI SDS 1.95 2", "character": 205},
            204: {"font_name": "ESRI SDS 1.95 2", "character": 204},
            203: {"font_name": "ESRI SDS 1.95 2", "character": 203},
            202: {"font_name": "ESRI SDS 1.95 2", "character": 202},
            198: {"font_name": "ESRI SDS 1.95 2", "character": 198},
            186: {"font_name": "ESRI SDS 1.95 2", "character": 186},
            185: {"font_name": "ESRI SDS 1.95 2", "character": 185},
            174: {"font_name": "ESRI SDS 1.95 2", "character": 174},
            44: {"font_name": "ESRI SDS 1.95 2", "character": 44},
            43: {"font_name": "ESRI SDS 1.95 2", "character": 43},
            39: {"font_name": "ESRI SDS 1.95 2", "character": 39},
        },
    }

    REPLACE_FONTS = {
        "SIGEOM_SYMBOL2": {
            61473: {"font_name": "SIGEOM_SYMBOL2", "unicode": 33},
            61474: {"font_name": "SIGEOM_SYMBOL2", "unicode": 34},
            61475: {"font_name": "SIGEOM_SYMBOL2", "unicode": 35},
            61476: {"font_name": "SIGEOM_SYMBOL2", "unicode": 36},
            61477: {"font_name": "SIGEOM_SYMBOL2", "unicode": 37},
            61478: {"font_name": "SIGEOM_SYMBOL2", "unicode": 38},
            61479: {"font_name": "SIGEOM_SYMBOL2", "unicode": 39},
            61480: {"font_name": "SIGEOM_SYMBOL2", "unicode": 40},
            61481: {"font_name": "SIGEOM_SYMBOL2", "unicode": 41},
            61482: {"font_name": "SIGEOM_SYMBOL2", "unicode": 42},
            61483: {"font_name": "SIGEOM_SYMBOL2", "unicode": 43},
            61484: {"font_name": "SIGEOM_SYMBOL2", "unicode": 44},
            61485: {"font_name": "SIGEOM_SYMBOL2", "unicode": 45},
            61486: {"font_name": "SIGEOM_SYMBOL2", "unicode": 46},
            61487: {"font_name": "SIGEOM_SYMBOL2", "unicode": 47},
            61488: {"font_name": "SIGEOM_SYMBOL2", "unicode": 48},
            61489: {"font_name": "SIGEOM_SYMBOL2", "unicode": 49},
            61490: {"font_name": "SIGEOM_SYMBOL2", "unicode": 50},
            61491: {"font_name": "SIGEOM_SYMBOL2", "unicode": 51},
            61492: {"font_name": "SIGEOM_SYMBOL2", "unicode": 52},
            61493: {"font_name": "SIGEOM_SYMBOL2", "unicode": 53},
            61494: {"font_name": "SIGEOM_SYMBOL2", "unicode": 54},
            61495: {"font_name": "SIGEOM_SYMBOL2", "unicode": 55},
            61496: {"font_name": "SIGEOM_SYMBOL2", "unicode": 56},
            61497: {"font_name": "SIGEOM_SYMBOL2", "unicode": 57},
        }
    }

    @staticmethod
    def can_convert_esri_font_character_to_simple_marker(
        font_name: str, character: int
    ) -> bool:
        """
        Returns True if a font marker with the specified name and character can be converted to a simple marker
        """
        if (
            font_name in SymbolConverter.ALIAS_FONTS
            and character in SymbolConverter.ALIAS_FONTS[font_name]
        ):
            alias = SymbolConverter.ALIAS_FONTS[font_name][character]
            font_name = alias["font_name"]
            character = alias["character"]

        return character in SymbolConverter.ESRI_FONTS_TO_QGIS_MARKERS.get(
            font_name, {}
        )

    @staticmethod
    def replace_char_if_needed(font_name: str, character: int) -> Tuple[str, int]:
        """
        Replaces font name and unicode using REPLACE_FONTS
        """
        if font_name in SymbolConverter.REPLACE_FONTS:
            if character in SymbolConverter.REPLACE_FONTS[font_name]:
                conversion = SymbolConverter.REPLACE_FONTS[font_name][character]
                font_name = conversion["font_name"]
                character = conversion["unicode"]
        return font_name, character

    @staticmethod
    def append_CharacterMarkerSymbolLayer(
        symbol,
        layer: CharacterMarkerSymbol,
        context: Context,
    ):
        """
        Appends a CharacterMarkerSymbolLayer to a symbol
        """
        if isinstance(layer, CharacterMarkerSymbol):
            font, unicode = SymbolConverter.replace_char_if_needed(
                layer.font, layer.unicode
            )

        if (
            context.convert_esri_fonts_to_simple_markers
            and SymbolConverter.can_convert_esri_font_character_to_simple_marker(
                font, unicode
            )
        ):
            SymbolConverter.append_CharacterMarkerSymbolLayerAsSimpleMarker(
                symbol, layer, context
            )
        elif context.convert_fonts:
            SymbolConverter.append_CharacterMarkerSymbolLayerAsSvg(
                symbol, layer, context
            )
        else:
            SymbolConverter.append_CharacterMarkerSymbolLayerAsFont(
                symbol, layer, context
            )

    @staticmethod
    def append_CharacterMarker3DSymbolLayer(
        symbol, layer: CharacterMarker3DSymbol, context: Context
    ):
        """
        Appends a CharacterMarker3DSymbol  to a symbol
        """
        context.push_warning(
            "3D character marker was converted to a 2d character marker"
        )

        if context.convert_fonts:
            SymbolConverter.append_CharacterMarkerSymbolLayerAsSvg(
                symbol, layer.character_marker_symbol, context
            )
        else:
            SymbolConverter.append_CharacterMarkerSymbolLayerAsFont(
                symbol, layer.character_marker_symbol, context
            )

    @staticmethod
    def append_CharacterMarkerSymbolLayerAsSimpleMarker(
        symbol,
        # pylint: disable=too-many-locals,too-many-statements,too-many-branches
        layer: CharacterMarkerSymbol,
        context: Context,
    ):
        """
        Appends a CharacterMarkerSymbolLayer to a symbol, converting the symbol to a simple qgis marker
        """

        font_name, character = SymbolConverter.replace_char_if_needed(
            layer.font, layer.unicode
        )

        vertical_offset_factor = 0
        if (
            font_name in SymbolConverter.ALIAS_FONTS
            and character in SymbolConverter.ALIAS_FONTS[font_name]
        ):
            alias = SymbolConverter.ALIAS_FONTS[font_name][character]
            font_name = alias["font_name"]
            character = alias["character"]
            vertical_offset_factor = alias.get("vertical_offset_factor", 0)

        conversion_properties = SymbolConverter.ESRI_FONTS_TO_QGIS_MARKERS[font_name][
            character
        ]
        if conversion_properties is None:
            if symbol.symbolLayerCount():
                # don't add an empty shell
                return

            # add an effectively "null" symbol
            out = QgsSimpleMarkerSymbolLayer(
                QgsSimpleMarkerSymbolLayerBase.Shape.Circle, 8
            )
            out.setColor(QColor(255, 255, 255, 0))
            out.setStrokeStyle(Qt.PenStyle.NoPen)
            out.setLocked(True)
            out.setEnabled(layer.enabled)
            symbol.appendSymbolLayer(out)
            context.symbol_layer_output_to_input_index_map[out] = (
                context.current_symbol_layer
            )
            return

        if not vertical_offset_factor:
            vertical_offset_factor = conversion_properties.get(
                "vertical_offset_factor", 0
            )
        horizontal_offset_factor = conversion_properties.get(
            "horizontal_offset_factor", 0
        )

        cim_polygon_symbol = None
        solid_stroke = None
        color = ColorConverter.color_to_qcolor(layer.color)

        angle_offset = 0
        outline_stroke_width = 1
        no_fix_outline_stroke_width = False
        size_scale_factor = 1
        if "shape" in conversion_properties:
            marker_type = conversion_properties.get(
                "shape", QgsSimpleMarkerSymbolLayerBase.Shape.Square
            )

            if marker_type in (
                QgsSimpleMarkerSymbolLayerBase.Shape.Circle,
                QgsSimpleMarkerSymbolLayerBase.Shape.Square,
                QgsSimpleMarkerSymbolLayerBase.Shape.Diamond,
                QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle,
                QgsSimpleMarkerSymbolLayerBase.Shape.Pentagon,
                QgsSimpleMarkerSymbolLayerBase.Shape.Line,
                QgsSimpleMarkerSymbolLayerBase.Shape.Cross,
                QgsSimpleMarkerSymbolLayerBase.Shape.CrossFill,
                QgsSimpleMarkerSymbolLayerBase.Shape.Cross2,
            ):
                simple_size = (1.47272 * layer.size) / 2.0
            elif marker_type == QgsSimpleMarkerSymbolLayerBase.Shape.Hexagon:
                simple_size = (1.47272 * layer.size * 4.178160 / 4.418160) / 2.0
            elif marker_type == QgsSimpleMarkerSymbolLayerBase.Shape.Star:
                simple_size = (1.47272 * layer.size * 12.327200 / 14.727200) / 2.0
            elif marker_type == QgsSimpleMarkerSymbolLayerBase.Shape.SemiCircle:
                simple_size = (1.47272 * layer.size * 12.327200 / 14.727200) / 2.0
            elif Qgis.QGIS_VERSION_INT >= 31800:
                if marker_type in (
                    QgsSimpleMarkerSymbolLayerBase.Shape.Octagon,
                    QgsSimpleMarkerSymbolLayerBase.Shape.SquareWithCorners,
                ):
                    simple_size = (1.3632460 * layer.size) / 2.0
                elif marker_type == QgsSimpleMarkerSymbolLayerBase.Shape.AsteriskFill:
                    simple_size = (1.3632460 * layer.size) / 2.0

            simple_size *= conversion_properties.get("shape_size_factor", 1)

            stroke_only_symbol = not QgsSimpleMarkerSymbolLayerBase.shapeIsFilled(
                marker_type
            ) or conversion_properties.get("outline_only", False)

            out = QgsSimpleMarkerSymbolLayer(
                marker_type, context.convert_size(simple_size)
            )

            y_offset = (layer.y_offset or 0) + vertical_offset_factor * simple_size
            x_offset = (layer.x_offset or 0) + horizontal_offset_factor * simple_size

            SHAPE_BASED_OFFSET = {
                QgsSimpleMarkerSymbolLayerBase.Shape.EquilateralTriangle: 1.8 / 14.7272,
                QgsSimpleMarkerSymbolLayerBase.Shape.Pentagon: 0.2 / 4.418160,
                QgsSimpleMarkerSymbolLayerBase.Shape.Star: 0.600000 / 12.327200,
            }

            if "cap_style" in conversion_properties:
                try:
                    out.setPenCapStyle(conversion_properties["cap_style"])
                except AttributeError:
                    pass

            if marker_type in SHAPE_BASED_OFFSET:
                y_offset -= simple_size * SHAPE_BASED_OFFSET[marker_type]

            if marker_type == QgsSimpleMarkerSymbolLayerBase.Shape.Hexagon:
                angle_offset += 90

            if "angle" in conversion_properties:
                angle_offset += conversion_properties["angle"]

            if marker_type == QgsSimpleMarkerSymbolLayerBase.Shape.Star:
                outline_stroke_width = (layer.size / 48) * 1.016667 / 0.416667
            else:
                outline_stroke_width = layer.size / 48

            outline_stroke_width *= conversion_properties.get("stroke_size_factor", 1)
            no_fix_outline_stroke_width = True

            if marker_type != QgsSimpleMarkerSymbolLayerBase.Shape.Star:
                size_scale_factor = 10.781760 / 11.781760

        elif "ellipse_marker_type" in conversion_properties:
            symbol_name = conversion_properties["ellipse_marker_type"]

            simple_size = (1.47272 * layer.size) / 2.0

            out = QgsEllipseSymbolLayer()
            out.setSymbolName(symbol_name)
            out.setSymbolWidth(
                simple_size * conversion_properties.get("width_factor", 1)
            )
            out.setSymbolHeight(
                simple_size * conversion_properties.get("height_factor", 1)
            )

            stroke_only_symbol = conversion_properties.get("outline_only", False)
            x_offset = (layer.x_offset or 0) + horizontal_offset_factor * simple_size
            y_offset = (layer.y_offset or 0) + vertical_offset_factor * simple_size
            outline_stroke_width = layer.size / 24

            outline_stroke_width *= conversion_properties.get("stroke_scale", 1)

            if "angle" in conversion_properties:
                angle_offset += conversion_properties["angle"]

        out.setSizeUnit(context.units)

        if isinstance(out, (QgsSimpleMarkerSymbolLayer, QgsEllipseSymbolLayer)):
            out.setPenJoinStyle(Qt.PenJoinStyle.MiterJoin)

        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked or character == 32)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)

        angle = ConversionUtils.convert_angle(layer.angle)

        angle += angle_offset
        out.setAngle(angle)

        offset_x = context.convert_size(x_offset)
        offset_y = -context.convert_size(y_offset)

        out.setOffset(
            # ConversionUtils.adjust_offset_for_rotation(
            QPointF(offset_x, offset_y)
        )
        #    angle))

        out.setOffsetUnit(context.units)

        if not stroke_only_symbol and isinstance(
            out, (QgsSimpleMarkerSymbolLayer, QgsEllipseSymbolLayer)
        ):
            out.setStrokeStyle(Qt.PenStyle.NoPen)

        appended_to_prev_layer = False

        if not stroke_only_symbol and isinstance(
            out, (QgsSimpleMarkerSymbolLayer, QgsEllipseSymbolLayer)
        ):
            out.setColor(color)
        else:
            if symbol.symbolLayerCount() > 0:
                prev_layer = symbol.symbolLayer(symbol.symbolLayerCount() - 1)
                prev_converted_layer = (
                    context.current_symbol.layers[context.current_symbol_layer - 1]
                    if context.current_symbol_layer
                    else None
                )

                # in ESRI land people often place outline only versions of font markers over the filled
                # versions in order to get an outline

                can_condense_to_simple_marker = (
                    prev_converted_layer is not None
                    and isinstance(out, QgsSimpleMarkerSymbolLayer)
                    and isinstance(prev_layer, QgsSimpleMarkerSymbolLayer)
                    and out.type() == prev_layer.type()
                    and out.shape() == prev_layer.shape()
                    and qgsDoubleNear(out.size(), prev_layer.size(), 0.01)
                    and qgsDoubleNear(prev_layer.angle(), out.angle(), 0.01)
                    and prev_layer.offset() == out.offset()
                    and prev_layer.strokeStyle() == Qt.PenStyle.NoPen
                )

                can_condense_to_ellipse_marker = (
                    prev_converted_layer is not None
                    and isinstance(out, QgsEllipseSymbolLayer)
                    and isinstance(prev_layer, QgsEllipseSymbolLayer)
                    and out.symbolName() == prev_layer.symbolName()
                    and qgsDoubleNear(prev_layer.size(), out.size(), 0.01)
                    and qgsDoubleNear(prev_layer.angle(), out.angle(), 0.01)
                    and prev_layer.offset() == out.offset()
                    and prev_layer.strokeStyle() == Qt.PenStyle.NoPen
                )

                if can_condense_to_simple_marker:
                    # effectively the same symbol, just an outline version of it! Let's make the results nice
                    # and QGIS-esque by just adding the stroke to the previous layer instead of creating a brand
                    # new layer
                    appended_to_prev_layer = True
                    prev_layer.setStrokeStyle(Qt.PenStyle.SolidLine)
                    prev_layer.setStrokeColor(color)
                    prev_layer.setStrokeWidth(
                        context.convert_size(context.fix_line_width(layer.size / 48))
                    )
                    prev_layer.setStrokeWidthUnit(context.units)
                elif can_condense_to_ellipse_marker:
                    # effectively the same symbol, just an outline version of it! Let's make the results nice
                    # and QGIS-esque by just adding the stroke to the previous layer instead of creating a brand
                    # new layer
                    appended_to_prev_layer = True
                    prev_layer.setStrokeStyle(Qt.PenStyle.SolidLine)
                    prev_layer.setStrokeColor(color)
                    prev_layer.setStrokeWidth(
                        context.convert_size(context.fix_line_width(layer.size / 24))
                    )
                    prev_layer.setStrokeWidthUnit(context.units)

            if not appended_to_prev_layer:
                out.setStrokeColor(color)

        if not appended_to_prev_layer and conversion_properties.get(
            "outline_only", False
        ):
            if not isinstance(
                out, QgsSimpleMarkerSymbolLayerBase
            ) or QgsSimpleMarkerSymbolLayerBase.shapeIsFilled(out.shape()):
                out.setColor(QColor(255, 255, 255, 0))
            if not no_fix_outline_stroke_width:
                out.setStrokeWidth(
                    context.convert_size(context.fix_line_width(outline_stroke_width))
                )
            else:
                out.setStrokeWidth(context.convert_size(outline_stroke_width))
            out.setStrokeWidthUnit(context.units)
            out.setSize(out.size() * size_scale_factor)

        if not appended_to_prev_layer:
            symbol.appendSymbolLayer(out)
            context.symbol_layer_output_to_input_index_map[out] = (
                context.current_symbol_layer
            )

        if "overlay" in conversion_properties:
            # Add overlay layer
            marker_type = conversion_properties["overlay"]

            overlay_size = out.size() * conversion_properties.get(
                "overlay_size_factor", 1
            )

            overlay = QgsSimpleMarkerSymbolLayer(marker_type, overlay_size)
            overlay.setSizeUnit(context.units)

            stroke_only_symbol = not QgsSimpleMarkerSymbolLayerBase.shapeIsFilled(
                marker_type
            )

            if not stroke_only_symbol:
                overlay.setColor(color)
                out.setStrokeStyle(Qt.PenStyle.NoPen)
            else:
                overlay.setStrokeColor(color)

            if marker_type == QgsSimpleMarkerSymbolLayerBase.Shape.Star:
                overlay.setStrokeWidth(
                    context.convert_size(
                        context.fix_line_width(layer.size / 48 * 1.016667 / 0.416667)
                    )
                )
            else:
                overlay.setStrokeWidth(
                    context.convert_size(context.fix_line_width(layer.size / 48))
                )
            overlay.setStrokeWidthUnit(context.units)

            overlay.setEnabled(layer.enabled)
            overlay.setLocked(layer.locked or character == 32)
            if layer.symbol_level != 0xFFFFFFFF:
                overlay.setRenderingPass(layer.symbol_level)

            angle = ConversionUtils.convert_angle(layer.angle)

            angle += conversion_properties.get("overlay_angle", 0)

            overlay.setAngle(angle)

            overlay.setOffset(
                ConversionUtils.adjust_offset_for_rotation(
                    QPointF(
                        context.convert_size(layer.x_offset or 0),
                        -context.convert_size(y_offset),
                    ),
                    angle,
                )
            )

            overlay.setOffset(
                QPointF(
                    overlay.offset().x(),
                    overlay.offset().y()
                    * conversion_properties.get("overlay_y_offset_factor", 0),
                )
            )

            overlay.setOffsetUnit(context.units)

            symbol.appendSymbolLayer(overlay)
            context.symbol_layer_output_to_input_index_map[overlay] = (
                context.current_symbol_layer
            )

        if "central_overlay" in conversion_properties:
            # Add central dot symbol layer
            marker_type = conversion_properties["central_overlay"]

            simple_size = (1.47272 * layer.size) / 2.0
            if marker_type == QgsSimpleMarkerSymbolLayerBase.Shape.Circle:
                simple_size *= 2.901760 / 11.781760
            elif marker_type == QgsSimpleMarkerSymbolLayerBase.Shape.Square:
                simple_size *= 2.041760 / 11.781760

            simple_size *= conversion_properties.get("overlay_size_factor", 1)

            stroke_only_symbol = not QgsSimpleMarkerSymbolLayerBase.shapeIsFilled(
                marker_type
            ) or conversion_properties.get("central_overlay_outline_only", False)

            out = QgsSimpleMarkerSymbolLayer(
                marker_type, context.convert_size(simple_size)
            )
            out.setSizeUnit(context.units)

            if not stroke_only_symbol:
                out.setColor(color)
                out.setStrokeStyle(Qt.PenStyle.NoPen)
            else:
                out.setColor(QColor(255, 255, 255, 0))
                out.setStrokeColor(color)
                out.setStrokeStyle(Qt.PenStyle.SolidLine)

                out.setStrokeWidth(
                    context.convert_size(
                        context.fix_line_width(
                            outline_stroke_width
                            * conversion_properties.get("overlay_stroke_factor", 1)
                        )
                    )
                )
                out.setStrokeWidthUnit(context.units)

            out.setEnabled(layer.enabled)
            out.setLocked(layer.locked or character == 32)
            if layer.symbol_level != 0xFFFFFFFF:
                out.setRenderingPass(layer.symbol_level)

            angle = ConversionUtils.convert_angle(layer.angle)

            angle += conversion_properties.get("overlay_angle", 0)

            out.setAngle(angle)

            out.setOffset(
                ConversionUtils.adjust_offset_for_rotation(
                    QPointF(
                        context.convert_size(layer.x_offset or 0),
                        -context.convert_size(y_offset),
                    ),
                    angle,
                )
            )

            if "overlay_y_offset" in conversion_properties:
                out.setOffset(
                    QPointF(
                        out.offset().x(),
                        out.size() * conversion_properties["overlay_y_offset"],
                    )
                )

            out.setOffset(
                QPointF(
                    out.offset().x(),
                    out.offset().y()
                    * conversion_properties.get("overlay_y_offset_factor", 1),
                )
            )

            out.setOffsetUnit(context.units)

            symbol.appendSymbolLayer(out)
            context.symbol_layer_output_to_input_index_map[out] = (
                context.current_symbol_layer
            )

    # pylint: disable=too-many-locals,too-many-statements,too-many-branches
    @staticmethod
    def append_CharacterMarkerSymbolLayerAsSvg(
        symbol,
        layer: CharacterMarkerSymbol,
        context: Context,
    ):
        """
        Appends a CharacterMarkerSymbolLayer to a symbol, rendering the font character
        to an SVG file.
        """

        font_family, unicode = SymbolConverter.replace_char_if_needed(
            layer.font, layer.unicode
        )

        if font_family not in ConversionUtils.available_font_families():
            context.push_warning("Font {} not available on system".format(font_family))
            return SymbolConverter.append_CharacterMarkerSymbolLayerAsFont(
                symbol, layer, context
            )

        character = chr(unicode)
        cim_solid_stroke = None
        color = ColorConverter.color_to_qcolor(layer.color)

        original_angle = layer.angle
        angle = ConversionUtils.convert_angle(layer.angle)

        font = QFont(font_family)
        if not font.exactMatch():
            # workaround for fonts with commas in name
            font.setFamilies([font_family])

        font.setPointSizeF(layer.size)

        # Using the rect of a painter path gives better results then using font metrics
        path = QPainterPath()
        path.setFillRule(Qt.FillRule.WindingFill)
        path.addText(0, 0, font, character)

        rect = path.boundingRect()

        font_bounding_rect = QFontMetricsF(font).boundingRect(character)

        # adjust size -- marker size in esri is the font size, svg marker size in qgis is the svg rect size
        scale = (
            rect.width() / font_bounding_rect.width()
            if font_bounding_rect.width()
            else 1
        )

        gen = QSvgGenerator()
        svg_path = SymbolConverter.symbol_name_to_filename(
            context.symbol_name, context.get_picture_store_folder(), "svg"
        )
        gen.setFileName(svg_path)
        gen.setViewBox(rect)

        painter = QPainter(gen)

        # todo -- size!

        painter.setBrush(QBrush(QColor(255, 0, 0)))
        painter.setPen(Qt.PenStyle.NoPen)
        painter.drawPath(path)
        painter.end()

        with open(svg_path, "r", encoding="utf-8") as f:
            t = f.read()

        t = t.replace("#ff0000", "param(fill)")
        t = t.replace('fill-opacity="1"', 'fill-opacity="param(fill-opacity)"')
        t = t.replace(
            'stroke="none"',
            'stroke="param(outline)" stroke-opacity="param(outline-opacity) 1" stroke-width="param(outline-width) 0"',
        )

        if context.embed_svgs():
            svg_content = base64.b64encode(t.encode("UTF-8")).decode("UTF-8")
            # no longer need the svg file
            try:
                os.remove(svg_path)
            except PermissionError:
                pass
            svg_path = "base64:{}".format(svg_content)
        else:
            with open(svg_path, "wt", encoding="utf-8") as f:
                f.write(t)

        out = QgsSvgMarkerSymbolLayer(svg_path)

        out.setSizeUnit(context.units)
        # esri symbol sizes are for height, QGIS are for width
        if out.defaultAspectRatio() != 1 and out.defaultAspectRatio() != 0:
            size = context.convert_size(scale * rect.width()) / out.defaultAspectRatio()
        else:
            size = context.convert_size(scale * rect.width())

        if (
            context.apply_conversion_tweaks
            and context.units == QgsUnitTypes.RenderUnit.RenderPoints
        ):
            # below this size QGIS can make the symbol invisible, it's less than one pixel
            size = max(size, 0.76)
        out.setSize(size)

        out.setAngle(angle)
        out.setFillColor(color)
        out.setStrokeWidth(0)

        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)

        offset_x = layer.x_offset or 0
        offset_y = layer.y_offset or 0
        out.setOffset(
            ConversionUtils.adjust_offset_for_rotation(
                QPointF(
                    context.convert_size(layer.x_offset or 0),
                    -context.convert_size(layer.y_offset or 0),
                ),
                original_angle,
            )
        )
        out.setOffsetUnit(context.units)

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

    # pylint: enable=too-many-locals,too-many-statements,too-many-branches

    # pylint: disable=too-many-locals,too-many-branches
    @staticmethod
    def append_CharacterMarkerSymbolLayerAsFont(
        symbol,
        layer: CharacterMarkerSymbol,
        context: Context,
    ):
        """
        Appends a CharacterMarkerSymbolLayer to a symbol, using QGIS font marker symbols
        """

        font_family, unicode = SymbolConverter.replace_char_if_needed(
            layer.font, layer.unicode
        )

        if font_family not in ConversionUtils.available_font_families():
            context.push_warning("Font {} not available on system".format(font_family))

        character = chr(unicode)

        cim_solid_stroke = None
        color = ColorConverter.color_to_qcolor(layer.color)

        # we need to calculate the character bounding box, as ESRI font marker symbols are rendered centered
        # on the character's bounding box (not the overall font metrics, like QGIS does)
        font = QFont(font_family)
        font.setPointSizeF(layer.size)
        font_metrics = QFontMetricsF(font)

        # This seems to give best conversion match to ESRI - vs tightBoundingRect
        rect = font_metrics.boundingRect(character)
        # note the font_metrics.width/ascent adjustments are here to reverse how QGIS offsets font markers
        try:
            x_offset_points = (
                rect.center().x() - font_metrics.horizontalAdvance(character) / 2.0
            )
        except AttributeError:
            x_offset_points = rect.center().x() - font_metrics.width(character) / 2.0
        y_offset_points = -rect.center().y() - font_metrics.ascent() / 2.0

        original_angle = 0
        original_angle = layer.angle
        angle = ConversionUtils.convert_angle(layer.angle)

        out = QgsFontMarkerSymbolLayer(
            font_family, character, context.convert_size(layer.size), color, angle
        )
        out.setSizeUnit(context.units)

        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)

        offset_x = layer.x_offset or 0
        offset_y = layer.y_offset or 0
        temp_offset = ConversionUtils.adjust_offset_for_rotation(
            QPointF(offset_x, -offset_y), original_angle
        )
        out.setOffset(
            QPointF(
                context.convert_size(temp_offset.x() + x_offset_points),
                context.convert_size(temp_offset.y() + y_offset_points),
            )
        )

        out.setOffsetUnit(context.units)

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

    # pylint: enable=too-many-locals,too-many-branches

    # pylint: disable=too-many-branches,too-many-statements
    @staticmethod
    def append_PictureMarkerSymbolLayer(
        symbol, layer: PictureMarkerSymbol, context: Context
    ):
        """
        Appends a PictureMarkerSymbolLayer to a symbol
        """

        picture = layer.picture
        if not picture:
            return

        if issubclass(picture.__class__, StdPicture):
            picture = picture.picture

        if issubclass(picture.__class__, EmfPicture) or (
            issubclass(picture.__class__, BmpPicture)
            and picture.format == BmpPicture.FORMAT_EMF
        ):
            if issubclass(picture.__class__, EmfPicture) or (
                issubclass(picture.__class__, BmpPicture)
                and picture.format == BmpPicture.FORMAT_EMF
            ):
                path = SymbolConverter.symbol_name_to_filename(
                    context.symbol_name, context.get_picture_store_folder(), "emf"
                )
                with open(path, "wb") as f:
                    f.write(picture.content)

                svg_path = SymbolConverter.symbol_name_to_filename(
                    context.symbol_name, context.get_picture_store_folder(), "svg"
                )
                SymbolConverter.emf_to_svg(
                    path, svg_path, inkscape_path=context.inkscape_path, context=context
                )
                # no longer need the emf file
                try:
                    os.remove(path)
                except PermissionError:
                    pass

                if not os.path.exists(svg_path):
                    context.push_warning(
                        "Conversion of EMF picture failed", level=Context.CRITICAL
                    )

                if context.embed_svgs() and os.path.exists(svg_path):
                    with open(svg_path, "rb") as svg_file:
                        svg_content = base64.b64encode(svg_file.read()).decode("UTF-8")
                    # no longer need the svg file
                    try:
                        os.remove(svg_path)
                    except PermissionError:
                        pass
                    svg_path = "base64:{}".format(svg_content)
            else:
                svg = PictureUtils.to_embedded_svg(
                    picture.content,
                    ColorConverter.color_to_qcolor(layer.color_foreground),
                    ColorConverter.color_to_qcolor(layer.color_background),
                    ColorConverter.color_to_qcolor(layer.color_transparent),
                )
                svg_base64 = base64.b64encode(svg.encode("UTF-8")).decode("UTF-8")
                svg_path = "base64:{}".format(svg_base64)

            out = QgsSvgMarkerSymbolLayer(
                svg_path, context.convert_size(layer.size), layer.angle
            )

            # esri symbol sizes are for height, QGIS are for width
            if out.defaultAspectRatio() != 1 and out.defaultAspectRatio() != 0:
                out.setSize(context.convert_size(layer.size) / out.defaultAspectRatio())

        else:
            picture_data = SymbolConverter.get_picture_data(
                picture,
                layer.color_foreground,
                layer.color_background,
                layer.color_transparent,
                context=context,
            )
            image_base64 = base64.b64encode(picture_data).decode("UTF-8")
            image_path = "base64:{}".format(image_base64)

            # unsure -- should this angle be converted? probably!
            out = QgsRasterMarkerSymbolLayer(
                image_path, context.convert_size(layer.size), layer.angle
            )

        out.setSizeUnit(context.units)
        out.setEnabled(layer.enabled)
        out.setLocked(layer.locked)
        if layer.symbol_level != 0xFFFFFFFF:
            out.setRenderingPass(layer.symbol_level)
        out.setOffset(
            ConversionUtils.adjust_offset_for_rotation(
                QPointF(
                    context.convert_size(layer.x_offset),
                    -context.convert_size(layer.y_offset),
                ),
                layer.angle,
            )
        )
        out.setOffsetUnit(context.units)

        symbol.appendSymbolLayer(out)
        context.symbol_layer_output_to_input_index_map[out] = (
            context.current_symbol_layer
        )

    # pylint: enable=too-many-branches,too-many-statements

    @staticmethod
    def convert_border_background_shadow(
        border: SymbolBorder,
        background: SymbolBackground,
        shadow: SymbolShadow,
        context: Context,
    ) -> QgsFillSymbol:
        """
        Converts symbol border/background/shadow to a QGIS fill symbol
        """
        fill_symbol = None

        if shadow:
            fill_symbol = SymbolConverter.Symbol_to_QgsSymbol(shadow.symbol, context)
            for i in range(fill_symbol.symbolLayerCount()):
                if isinstance(
                    fill_symbol.symbolLayer(i),
                    (
                        QgsGradientFillSymbolLayer,
                        QgsRasterFillSymbolLayer,
                        QgsShapeburstFillSymbolLayer,
                        QgsSimpleFillSymbolLayer,
                    ),
                ):
                    fill_symbol.symbolLayer(i).setOffset(
                        QPointF(shadow.x_offset, -shadow.y_offset)
                    )
                    fill_symbol.symbolLayer(i).setOffsetUnit(
                        QgsUnitTypes.RenderUnit.RenderPoints
                    )

        if background:
            background_symbol = SymbolConverter.Symbol_to_QgsSymbol(
                background.symbol, context
            )
            if fill_symbol:
                for i in range(background_symbol.symbolLayerCount()):
                    fill_symbol.appendSymbolLayer(
                        background_symbol.symbolLayer(i).clone()
                    )
            else:
                fill_symbol = QgsFillSymbol()
                fill_symbol.changeSymbolLayer(
                    0, background_symbol.symbolLayer(0).clone()
                )
                for i in range(1, background_symbol.symbolLayerCount()):
                    fill_symbol.appendSymbolLayer(
                        background_symbol.symbolLayer(i).clone()
                    )

        if border:
            border_symbol = SymbolConverter.Symbol_to_QgsSymbol(border.symbol, context)
            if fill_symbol:
                for i in range(border_symbol.symbolLayerCount()):
                    fill_symbol.appendSymbolLayer(border_symbol.symbolLayer(i).clone())
            else:
                fill_symbol = QgsFillSymbol()
                fill_symbol.changeSymbolLayer(0, border_symbol.symbolLayer(0).clone())
                for i in range(1, border_symbol.symbolLayerCount()):
                    fill_symbol.appendSymbolLayer(border_symbol.symbolLayer(i).clone())

        return fill_symbol

    @staticmethod
    def reverse_line_symbol(symbol: QgsLineSymbol) -> QgsLineSymbol:
        """
        Reverses a line symbol, eg placing start vertex markers at the end
        """
        res = symbol.clone()
        for i in range(res.symbolLayerCount()):
            layer = res.symbolLayer(i)
            if isinstance(layer, QgsMarkerLineSymbolLayer):
                if layer.placement() == QgsMarkerLineSymbolLayer.Placement.FirstVertex:
                    layer.setPlacement(QgsMarkerLineSymbolLayer.Placement.LastVertex)
                elif layer.placement() == QgsMarkerLineSymbolLayer.Placement.LastVertex:
                    layer.setPlacement(QgsMarkerLineSymbolLayer.Placement.FirstVertex)

            # TODO - other symbol layer types?

        return res
