current commit
260809.1.4 · https://gitlab.com/saalen/highlight/-/work_items/240 emit the IndentGuide style and colour only when guides are enabled
/***************************************************************************
                     svggenerator.cpp  -  description
                             -------------------
    begin                : Mo 23.06.2008
    copyright            : (C) 2008-2026 by Andre Simon
    email                : a.simon@mailbox.org
 ***************************************************************************/


/*
This file is part of Highlight.

Highlight 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 3 of the License, or
(at your option) any later version.

Highlight is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Highlight.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <string>
#include <sstream>

#include "version.h"
#include "svggenerator.h"

using std::ostringstream;
using std::string;

namespace highlight
{

SVGGenerator::SVGGenerator()
    : CodeGenerator ( SVG )
{
    spacer = initialSpacer = "&#160;";
    maskWs=true;
    newLineTag = "\n";
    styleCommentOpen="/*";
    styleCommentClose="*/";
}

void SVGGenerator::initOutputTags()
{
    openTags = {
        "",
        getOpenTag ( STY_NAME_STR ),
        getOpenTag ( STY_NAME_NUM ),
        getOpenTag ( STY_NAME_SLC ),
        getOpenTag ( STY_NAME_COM ),
        getOpenTag ( STY_NAME_ESC ),
        getOpenTag ( STY_NAME_DIR ),
        getOpenTag ( STY_NAME_DST ),
        getOpenTag ( STY_NAME_LIN ),
        getOpenTag ( STY_NAME_SYM ),
        getOpenTag ( STY_NAME_IPL ),
        getOpenTag ( STY_NAME_ERR ),
        getOpenTag ( STY_NAME_ERM )
    };

    closeTags.assign ( NUMBER_BUILTIN_STATES, "</tspan>" );
    closeTags[0] = "";

    guideTagOpen = getOpenTag ( STY_NAME_GUI );
    guideTagClose = "</tspan>";
}

string SVGGenerator::getStyleDefinition()
{
    if (disableStyleCache || styleDefinitionCache.empty() ) {
        ostringstream os;
        string tspan("tspan.");
        if ( includeStyleDef ) {
            os << "<style type=\"text/css\">\n"
               << "<![CDATA[\n";
        }
        os << "rect { fill:#"
           << ( docStyle.getBgColour().getRed ( SVG ) )
           << ( docStyle.getBgColour().getGreen ( SVG ) )
           << ( docStyle.getBgColour().getBlue ( SVG ) )
           << "; } \n";
        os << "g { font-size: " << getBaseFontSize();
        os << "; font-family: " << getBaseFont() << "; white-space: pre;}\n";
        os << getAttributes ( "text", docStyle.getDefaultStyle() )
           << getAttributes ( tspan+STY_NAME_NUM, docStyle.getNumberStyle() )
           << getAttributes ( tspan+STY_NAME_ESC, docStyle.getEscapeCharStyle() )
           << getAttributes ( tspan+STY_NAME_STR, docStyle.getStringStyle() )
           << getAttributes ( tspan+STY_NAME_DST, docStyle.getPreProcStringStyle() )
           << getAttributes ( tspan+STY_NAME_SLC, docStyle.getSingleLineCommentStyle() )
           << getAttributes ( tspan+STY_NAME_COM, docStyle.getCommentStyle() )
           << getAttributes ( tspan+STY_NAME_DIR, docStyle.getPreProcessorStyle() )
           << getAttributes ( tspan+STY_NAME_SYM, docStyle.getOperatorStyle() )
           << getAttributes ( tspan+STY_NAME_IPL, docStyle.getInterpolationStyle() )
           << getAttributes ( tspan+STY_NAME_LIN, docStyle.getLineStyle() );

           os << getAttributes ( tspan+STY_NAME_ERM, docStyle.getErrorMessageStyle() );
           os << getAttributes ( tspan+STY_NAME_ERR, docStyle.getErrorStyle() );
           if ( showIndentGuides ) {
               os << getAttributes ( tspan+STY_NAME_GUI, docStyle.getIndentGuideStyle() );
           }

        for ( const auto &[first, second] : docStyle.getKeywordStyles() ) {
            os << getAttributes ( tspan+first, second );
        }

        if ( includeStyleDef ) {
            os << "]]>\n"
               << "</style>";
        }
        styleDefinitionCache=os.str();
    }
    return styleDefinitionCache;
}


string SVGGenerator::getAttributes ( const string & elemName,
                                     const ElementStyle & elem )
{
    ostringstream s;

    if ( !elemName.empty() ) {
        s << elemName << " { ";
    }

    if (!elem.getCustomOverride()) {
        s   << "fill:#"
            << ( elem.getColour().getRed ( HTML ) )
            << ( elem.getColour().getGreen ( HTML ) )
            << ( elem.getColour().getBlue ( HTML ) )
            << ( elem.isBold() ?     "; font-weight:bold" :"" )
            << ( elem.isItalic() ?   "; font-style:italic" :"" )
            << ( elem.isUnderline() ? "; text-decoration:underline" :"" );
    }

    if (string customStyle = elem.getCustomAttribute(); !customStyle.empty()) {
        if (!elem.getCustomOverride()) {
            s << "; ";
        }
        s << customStyle;
    }

    if ( !elemName.empty() ) {
        s << "; }\n" ;
    }
    return s.str();
}

string  SVGGenerator::getOpenTag ( const string& styleName )
{
    return "<tspan class=\""+styleName+"\">";
}

string SVGGenerator::getHeader()
{
    ostringstream header;
    header << "<?xml version=\"1.0\"";
    if ( encodingDefined() ) {
        header << " encoding=\"" << encoding << "\"";
    }
    header << "?>\n";
    if ( !includeStyleDef ) {
        header  << R"(<?xml-stylesheet type="text/css" href=")"
                << getStyleOutputPath()
                << "\"?>\n";
    }
    header << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.2//EN\" "
           << "\"http://www.w3.org/Graphics/SVG/1.2/DTD/svg12.dtd\">\n";
    header << R"(<svg xmlns="http://www.w3.org/2000/svg" version="1.2" )"
           << R"(baseProfile="full" xml:space="preserve")";
    if ( width.size() ) header << " width=\""<<width<<"\"";
    if ( height.size() ) header << " height=\""<<height<<"\"";
    //viewBox=\"0 0 800 600\"
    header << ">\n<desc>" << docTitle << "</desc>\n";
    if ( includeStyleDef ) {
        header << "<defs>\n";
        header << getStyleDefinition();
        header << "\n</defs>\n";
    }
    return header.str();
}

void SVGGenerator::printBody()
{
    *out << "<g>\n<rect x=\"0\" y=\"0\" width=\"100%\" height=\"100%\"/>"; // rect: background color
    int fontSize=0;
    StringTools::str2num<int> ( fontSize, getBaseFontSize(), std::dec );
    *out << "\n<text x=\"10\" y=\""<<fontSize*2<<"\">";
    processRootState();
    *out << "</text>\n</g>\n";
}


string SVGGenerator::getFooter()
{
    ostringstream os;
    os <<"</svg>\n";

    if (!omitVersionComment) {
        os  << "<!-- SVG generated by Highlight "
            << highlight::Info::getVersion()
            << ", "
            << highlight::Info::getWebsite()
            <<" -->\n";
    }
    return os.str();
}

string SVGGenerator::maskCharacter ( unsigned  char c )
{
    switch ( c ) {
    case ' ' :
        return spacer;
    case '<' :
        return "&lt;";
    case '>' :
        return "&gt;";
    case '&' :
        return "&amp;";
    case '\"' :
        return "&quot;";
    default:
        return string ( 1, c );
    }
}

string SVGGenerator::getKeywordOpenTag ( unsigned int styleID )
{
    return getOpenTag ( currentSyntax->getKeywordClasses() [styleID] );
}

string SVGGenerator::getKeywordCloseTag ( unsigned int styleID )
{
    return "</tspan>";
}

string SVGGenerator::getNewLine()
{
    ostringstream ss;

    printSyntaxError(ss);

    if ( lineNumber>1 ) {
        int fontSize=0;
        StringTools::str2num<int> ( fontSize, getBaseFontSize(), std::dec );
        ss<< "</text>\n<text x=\"10\" y=\""<< ( lineNumber*fontSize*2 ) <<"\">";
    }
    return ss.str();
}

void SVGGenerator::setSVGSize ( const string& w, const string& h )
{
    width=w;
    height=h;
}

}