@ 261105.1
current
/***************************************************************************
                          themereader.cpp  -  description
                             -------------------
    begin                : Son Nov 10 2002
    copyright            : (C) 2002-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 "themereader.h"
#include "keystore.h"
#include <Diluculum/LuaState.hpp>
#include <sstream>
#include <iostream>
#include <cmath>
#include <iomanip>

#include <string_view>

using std::string;
using std::string_view;
using std::vector;

namespace highlight
{

    ThemeReader::ThemeReader()  
{}

ThemeReader::~ThemeReader()
{
    for (auto* chunk : pluginChunks) {
        delete chunk;
    }
}

OutputType ThemeReader::getOutputType(string_view typeDesc) {

    if (typeDesc=="html" || typeDesc=="xhtml") return HTML;

    if (typeDesc=="rtf") return RTF;
    if (typeDesc=="latex") return LATEX;
    if (typeDesc=="tex") return TEX;
    if (typeDesc=="ansi") return ESC_ANSI;
    if (typeDesc=="xterm256") return ESC_XTERM256;
    if (typeDesc=="truecolor") return ESC_TRUECOLOR;

    if (typeDesc=="svg") return SVG;
    if (typeDesc=="bbcode") return BBCODE;
    if (typeDesc=="pango") return PANGO;
    if (typeDesc=="odt") return ODTFLAT;
    return HTML;
}

static Colour blend(const Colour& fg, const Colour& bg, double f)
{
    std::ostringstream os;
    os << '#' << std::hex << std::setfill('0');
    os << std::setw(2) << (int)(fg.getRed()   * f + bg.getRed()   * (1-f));
    os << std::setw(2) << (int)(fg.getGreen() * f + bg.getGreen() * (1-f));
    os << std::setw(2) << (int)(fg.getBlue()  * f + bg.getBlue()  * (1-f));
    return Colour(os.str());
}

void ThemeReader::initStyle(ElementStyle& style, const Diluculum::LuaVariable& var)
{

    style.setCustomAttribute(""); // Init with a default value.

    if (var["Custom"].value()!=Diluculum::Nil) {

        int idx=1;

        while (var["Custom"][idx].value() !=Diluculum::Nil) {

            if (getOutputType(var["Custom"][idx]["Format"].value().asString()) == outputType) {

                style.setCustomAttribute (var["Custom"][idx]["Style"].value().asString() );
                style.setCustomOverride ( true );
                break;
            }
            idx++;
        }
    }

    string styleColor="#000000";
    bool styleBold=false, styleItalic=false, styleUnderline=false;
    int styleAnsiIndex=-1;

    if (var["Colour"].value()!=Diluculum::Nil) {
        styleColor= var["Colour"].value().asString();
        style.setCustomOverride(false);
    }

    if (var["Bold"].value()!=Diluculum::Nil) {
        styleBold= var["Bold"].value().asBoolean();
        style.setCustomOverride(false);
    }

    if (var["Italic"].value()!=Diluculum::Nil) {
        styleItalic= var["Italic"].value().asBoolean();
        style.setCustomOverride(false);
    }

    if (var["Underline"].value()!=Diluculum::Nil) {
        styleUnderline= var["Underline"].value().asBoolean();
        style.setCustomOverride(false);
    }

    if (var["AnsiIndex"].value()!=Diluculum::Nil) {
        lua_Integer idx = var["AnsiIndex"].value().asInteger();
        if (idx>=0 && idx<=15) styleAnsiIndex=idx;
    }

    style.setColour(Colour(styleColor));
    style.setBold(styleBold);
    style.setItalic(styleItalic);
    style.setUnderline(styleUnderline);
    style.setAnsiIndex(styleAnsiIndex);
}

bool ThemeReader::load ( const string &styleDefinitionPath , OutputType type, bool loadSemanticStyles)
{
    try {

        outputType = type;

        fileOK=true;

        Diluculum::LuaState ls;

        ls["HL_FORMAT_HTML"]=HTML;
        ls["HL_FORMAT_XHTML"]=XHTML;
        ls["HL_FORMAT_TEX"]=TEX;
        ls["HL_FORMAT_LATEX"]=LATEX;
        ls["HL_FORMAT_RTF"]=RTF;
        ls["HL_FORMAT_ANSI"]=ESC_ANSI;
        ls["HL_FORMAT_XTERM256"]=ESC_XTERM256;
        ls["HL_FORMAT_TRUECOLOR"]=ESC_TRUECOLOR;
        ls["HL_FORMAT_SVG"]=SVG;
        ls["HL_FORMAT_BBCODE"]=BBCODE;
        ls["HL_FORMAT_PANGO"]=PANGO;
        ls["HL_FORMAT_ODT"]=ODTFLAT;
        ls["HL_OUTPUT"] = outputType;
        ls.doString("Injections={}");

        lua_register (ls.getState(), "StoreValue", KeyStore::luaStore);

        ls.doFile (styleDefinitionPath);

        desc = ls["Description"].value().asString();

        if (ls["Categories"].value() !=Diluculum::Nil){

            Diluculum::LuaValueMap categoryMap;
            categoryMap = ls["Categories"].value().asTable();

            for(auto it = categoryMap.begin(); it != categoryMap.end(); ++it)
            {
                categories.append(it->second.asString());
                if (std::next(it) != categoryMap.end()) {
                    categories.append(",");
                }
            }
        }

        if (pluginChunks.size()) {
            Diluculum::LuaValueList params;
            params.emplace_back(desc);
            for (auto* chunk : pluginChunks) {
                ls.call(*chunk, params, "theme user function");
            }
        }

        initStyle(canvas, ls["Canvas"]);
        initStyle(defaultElem, ls["Default"]);
        initStyle(comment, ls["BlockComment"]);
        initStyle(slcomment, ls["LineComment"]);
        initStyle(directive, ls["PreProcessor"]);
        initStyle(str, ls["String"]);
        initStyle(escapeChar, ls["Escape"]);
        initStyle(interpolation, ls["Interpolation"]);
        initStyle(number, ls["Number"]);
        initStyle(dstr, ls["StringPreProc"]);
        initStyle(line, ls["LineNum"]);
        initStyle(operators, ls["Operator"]);

        errorMessages.setBold(true);
        errorMessages.setColour(Colour("#ff0000"));

        if (outputType==HTML || outputType==XHTML) {
            hover.setCustomOverride(true);
            hover.setCustomAttribute ("cursor:help");
            errorMessages.setCustomAttribute ("border:solid 1px red; margin-left: 3em");

            if (line.getCustomAttribute().empty()) {
                line.setCustomAttribute("user-select: none;-webkit-user-select: none");
            }
        }

        if (outputType==LATEX) {
            errorMessages.setCustomOverride(true);
            errorMessages.setCustomAttribute (R"(\marginpar{\small\itshape\color{red}#1})");
        }

        if (ls["ErrorMessage"].value() !=Diluculum::Nil){
            initStyle(errorMessages, ls["ErrorMessage"]);
        }

        if (ls["Hover"].value() !=Diluculum::Nil){
            initStyle(hover, ls["Hover"]);
        }

        errors.setBold(true);
        errors.setColour(Colour("#ff0000"));
        if (ls["Error"].value() !=Diluculum::Nil){
            initStyle(errors, ls["Error"]);
        }

        // faint by construction: a quarter step from the canvas towards the text colour
        indentGuide.setColour(blend(defaultElem.getColour(), canvas.getColour(), 0.25));
        if (ls["IndentGuide"].value() !=Diluculum::Nil){
            initStyle(indentGuide, ls["IndentGuide"]);
        }

        keywordStyles.clear();
        semanticStyleMap.clear();

        int idx=1;
        ElementStyle kwStyle;
        char kwName[5];
        while (ls["Keywords"][idx].value() !=Diluculum::Nil && idx < 27) {
            initStyle(kwStyle, ls["Keywords"][idx]);
            snprintf(kwName, sizeof(kwName), "kw%c", ('a'+idx-1));
            keywordStyles.insert ( make_pair ( string(kwName), kwStyle ));
            idx++;
        }

        keywordStyleCnt = idx - 1;

        if (loadSemanticStyles && ls["SemanticTokenTypes"].value() !=Diluculum::Nil) {

            idx=1;
            while (ls["SemanticTokenTypes"][idx].value() !=Diluculum::Nil && idx < 27) {

                initStyle(kwStyle, ls["SemanticTokenTypes"][idx]["Style"]);
                snprintf(kwName, sizeof(kwName), "st%c", ('a'+idx+-1));
                keywordStyles.insert ( make_pair ( string(kwName), kwStyle ));
                semanticStyleMap[ls["SemanticTokenTypes"][idx]["Type"].value().asString()] = keywordStyleCnt + idx;
                idx++;
            }
        }

        originalStyles=keywordStyles;

        idx=1;
        while (ls["Injections"][idx].value() !=Diluculum::Nil) {
            themeInjections +=ls["Injections"][idx].value().asString();
            idx++;
        }

    } catch (Diluculum::LuaFileError &err) {
        errorMsg = string(err.what());
        return fileOK=false;
    } catch (Diluculum::TypeMismatchError &err) {
        errorMsg = string(err.what());
        return fileOK=false;
    } catch (Diluculum::LuaSyntaxError &err) {
        errorMsg = "syntax error: "+string(err.what());
        return fileOK=false;
    }

    return fileOK;
}

int ThemeReader::getSemanticStyle(string_view type) {
    if (auto it = semanticStyleMap.find(type); it != semanticStyleMap.end()) {
        return it->second;
    }
    return 0;
}

int ThemeReader::getSemanticTokenStyleCount() const {
    return semanticStyleMap.size();
}

int ThemeReader::getKeywordStyleCount() const {
    return keywordStyleCnt;
}

string ThemeReader::getErrorMessage() const
{
    return errorMsg;
}

Colour ThemeReader::getBgColour() const
{
    return canvas.getColour();
}

ElementStyle ThemeReader::getDefaultStyle() const
{
    return defaultElem;
}

ElementStyle ThemeReader::getCommentStyle() const
{
    return comment;
}

ElementStyle ThemeReader::getSingleLineCommentStyle() const
{
    return slcomment;
}

ElementStyle ThemeReader::getStringStyle() const
{
    return str;
}

ElementStyle ThemeReader::getPreProcStringStyle() const
{
    return dstr;
}

ElementStyle ThemeReader::getEscapeCharStyle() const
{
    return escapeChar;
}

ElementStyle ThemeReader::getInterpolationStyle() const
{
    return interpolation;
}

ElementStyle ThemeReader::getNumberStyle() const
{
    return number;
}

ElementStyle ThemeReader::getPreProcessorStyle() const
{
    return directive;
}

ElementStyle ThemeReader::getLineStyle() const
{
    return line;
}

ElementStyle ThemeReader::getOperatorStyle() const
{
    return operators;
}

ElementStyle ThemeReader::getHoverStyle() const
{
    return hover;
}

ElementStyle ThemeReader::getErrorStyle() const
{
    return errors;
}

ElementStyle ThemeReader::getErrorMessageStyle() const
{
    return errorMessages;
}

ElementStyle ThemeReader::getIndentGuideStyle() const
{
    return indentGuide;
}

bool ThemeReader::found () const
{
    return fileOK;
}

ElementStyle ThemeReader::getKeywordStyle ( string_view className )
{
    if (auto it = keywordStyles.find(className); it != keywordStyles.end()) {
        return it->second;
    }
    return defaultElem;
}

vector <string> ThemeReader::getClassNames() const
{
    vector <string> kwClassNames;

    for ( const auto &[first, second] : keywordStyles ) {
        kwClassNames.push_back(first);
    }

    return kwClassNames;
}

KeywordStyles ThemeReader::getKeywordStyles() const
{
    return keywordStyles;
}

string ThemeReader::getInjections() const
{
    return themeInjections;
}

void ThemeReader::overrideAttributes(vector<int>& attributes) {

    if (dirtyAttributes)
        keywordStyles=originalStyles;

    for ( int attr : attributes)
    {
        int kwGroup = attr & 0xf;
        char kwName[5];
        snprintf(kwName, sizeof(kwName), "kw%c", ('a'+kwGroup-1));

        if (keywordStyles.count ( kwName)) {

            ElementStyle elem = keywordStyles[kwName];
            if (attr & 128) elem.setBold(true);
            if (attr & 256) elem.setItalic(true);
            if (attr & 512) elem.setUnderline(true);
            if (attr & 1024) elem.setBold(false);
            if (attr & 2048) elem.setItalic(false);
            if (attr & 4096) elem.setUnderline(false);
            keywordStyles[kwName] = elem;
            dirtyAttributes = true;
        }
    }
}

float ThemeReader::getsRGB(int rgbValue) const {
	float s = (float)rgbValue / 255;
	s = (s <= 0.03928) ? s / 12.92 : std::pow(((s + 0.055) / 1.055), 2.4);
	return s;
}

float ThemeReader::getBrightness(const Colour& colour) const {
    return  0.2126*getsRGB(colour.getRed()) +
            0.7152*getsRGB(colour.getGreen()) +
            0.0722*getsRGB(colour.getBlue());
}
float ThemeReader::getContrast() const {
    float canvasBrightness = getBrightness(canvas.getColour());
    float defaultBrightness = getBrightness(defaultElem.getColour());
    return  (std::max(canvasBrightness, defaultBrightness) + 0.05) /
            (std::min(canvasBrightness, defaultBrightness) + 0.05);
}

}