#include "documentstyle.hpp" #include "kristall.hpp" #include #include #include #include #include #include #include #include #include #include #include DocumentStyle::DefaultFonts::DefaultFonts() { // Initialise default fonts #ifdef Q_OS_WIN32 // Windows default fonts are ugly, so we use standard ones. this->regular = "Segoe UI"; this->fixed = "Consolas"; #else // *nix regular = QFontDatabase::systemFont(QFontDatabase::GeneralFont).family(); fixed = QFontInfo(QFont("monospace")).family(); #endif } static QString encodeCssFont (const QFont& refFont) { //----------------------------------------------------------------------- // This function assembles a CSS Font specification string from // a QFont. This supports most of the QFont attributes settable in // the Qt 4.8 and Qt 5.3 QFontDialog. // // (1) Font Family // (2) Font Weight (just bold or not) // (3) Font Style (possibly Italic or Oblique) // (4) Font Size (in either pixels or points) // (5) Decorations (possibly Underline or Strikeout) // // Not supported: Writing System (e.g. Latin). // // See the corresponding decode function, below. // QFont decodeCssFontString (const QString cssFontStr) //----------------------------------------------------------------------- QStringList fields; // CSS font attribute fields // *************************************************** // *** (1) Font Family: Primary plus Substitutes *** // *************************************************** const QString family = refFont.family(); // NOTE [9-2014, Qt 4.8.6]: This isn't what I thought it was. It // does not return a list of "fallback" font faces (e.g. Georgia, // Serif for "Times New Roman"). In my testing, this is always // returning an empty list. // QStringList famSubs = QFont::substitutes (family); if (!famSubs.contains (family)) famSubs.prepend (family); static const QChar DBL_QUOT ('"'); const int famCnt = famSubs.count(); QStringList famList; for (int inx = 0; inx < famCnt; ++inx) { // Place double quotes around family names having space characters, // but only if double quotes are not already there. // const QString fam = famSubs [inx]; if (fam.contains (' ') && !fam.startsWith (DBL_QUOT)) famList << (DBL_QUOT + fam + DBL_QUOT); else famList << fam; } const QString famStr = QString ("font-family: ") + famList.join (", "); fields << famStr; // ************************************** // *** (2) Font Weight: Bold or Not *** // ************************************** const bool bold = refFont.bold(); if (bold) fields << "font-weight: bold"; // **************************************************** // *** (3) Font Style: possibly Italic or Oblique *** // **************************************************** const QFont::Style style = refFont.style(); switch (style) { case QFont::StyleNormal: break; case QFont::StyleItalic: fields << "font-style: italic"; break; case QFont::StyleOblique: fields << "font-style: oblique"; break; } // ************************************************ // *** (4) Font Size: either Pixels or Points *** // ************************************************ const double sizeInPoints = refFont.pointSizeF(); // <= 0 if not defined. const int sizeInPixels = refFont.pixelSize(); // <= 0 if not defined. if (sizeInPoints > 0.0) fields << QString ("font-size: %1pt") .arg (sizeInPoints); else if (sizeInPixels > 0) fields << QString ("font-size: %1px") .arg (sizeInPixels); // *********************************************** // *** (5) Decorations: Underline, Strikeout *** // *********************************************** const bool underline = refFont.underline(); const bool strikeOut = refFont.strikeOut(); if (underline && strikeOut) fields << "text-decoration: underline line-through"; else if (underline) fields << "text-decoration: underline"; else if (strikeOut) fields << "text-decoration: line-through"; const QString cssFontStr = fields.join ("; "); return cssFontStr; } DocumentStyle::DocumentStyle() : theme(Fixed), standard_font(), h1_font(), h2_font(), h3_font(), preformatted_font(), blockquote_font(), background_color(0xed, 0xef, 0xff), standard_color(0x00, 0x00, 0x00), preformatted_color(0x00, 0x00, 0x00), h1_color(0x02, 0x2f, 0x90), h2_color(0x02, 0x2f, 0x90), h3_color(0x02, 0x2f, 0x90), blockquote_fgcolor(0x00, 0x00, 0x00), blockquote_bgcolor(0xFF, 0xFF, 0xFF), internal_link_color(0x0e, 0x8f, 0xff), external_link_color(0x0e, 0x8f, 0xff), cross_scheme_link_color(0x09, 0x60, 0xa7), internal_link_prefix("→ "), external_link_prefix("⇒ "), margin_h(30.0), margin_v(55.0), text_width(900), ansi_colors({"black", "darkred", "darkgreen", "darkgoldenrod", "darkblue", "darkmagenta", "darkcyan", "lightgray", "gray", "red", "green", "goldenrod", "lightblue", "magenta", "cyan", "white"}), justify_text(true), text_width_enabled(true), centre_h1(false), line_height_p(5.0), line_height_h(5.0), indent_bq(1), indent_p(1), indent_h(0), indent_l(2), indent_size(15.0), list_symbol(QTextListFormat::ListDisc) { this->initialiseDefaultFonts(); } void DocumentStyle::initialiseDefaultFonts() { DefaultFonts default_fonts; preformatted_font.setFamily(default_fonts.fixed); preformatted_font.setPointSizeF(12.0); standard_font.setFamily(default_fonts.regular); standard_font.setPointSizeF(12.0); h1_font.setFamily(default_fonts.regular); h1_font.setBold(true); h1_font.setPointSizeF(22.0); h2_font.setFamily(default_fonts.regular); h2_font.setBold(true); h2_font.setPointSizeF(17.0); h3_font.setFamily(default_fonts.regular); h3_font.setBold(true); h3_font.setPointSizeF(14.0); blockquote_font.setFamily(default_fonts.regular); blockquote_font.setItalic(true); blockquote_font.setPointSizeF(12.0); } QString DocumentStyle::createFileNameFromName(const QString &src, int index) { QString result; result.reserve(src.size() + 5); for(int i = 0; i < src.size(); i++) { QChar c = src.at(i); if(c.isLetterOrNumber()) { result.append(c.toLower()); } else if(c.isSpace()) { result.append('-'); } else { result.append(QString::number(c.unicode())); } } if(index > 0) { result.append(QString("-%1").arg(index)); } result.append(".kthm"); return result; } bool DocumentStyle::save(QSettings &settings) const { settings.setValue("version", 1); settings.setValue("theme", int(theme)); settings.setValue("background_color", background_color.name()); settings.setValue("blockquote_color", blockquote_bgcolor.name()); settings.setValue("margins_h", margin_h); settings.setValue("margins_v", margin_v); settings.setValue("ansi_colors", ansi_colors); { settings.beginGroup("Standard"); settings.setValue("font", standard_font.toString()); settings.setValue("color", standard_color.name()); settings.endGroup(); } { settings.beginGroup("Preformatted"); settings.setValue("font", preformatted_font.toString()); settings.setValue("color", preformatted_color.name()); settings.endGroup(); } { settings.beginGroup("H1"); settings.setValue("font", h1_font.toString()); settings.setValue("color", h1_color.name()); settings.endGroup(); } { settings.beginGroup("H2"); settings.setValue("font", h2_font.toString()); settings.setValue("color", h2_color.name()); settings.endGroup(); } { settings.beginGroup("H3"); settings.setValue("font", h3_font.toString()); settings.setValue("color", h3_color.name()); settings.endGroup(); } { settings.beginGroup("Blockquote"); settings.setValue("font", blockquote_font.toString()); settings.setValue("color", blockquote_fgcolor.name()); settings.endGroup(); } { settings.beginGroup("Link"); settings.setValue("color_internal", internal_link_color.name()); settings.setValue("color_external", external_link_color.name()); settings.setValue("color_cross_scheme", cross_scheme_link_color.name()); settings.setValue("internal_prefix", internal_link_prefix); settings.setValue("external_prefix", external_link_prefix); settings.endGroup(); } { settings.beginGroup("Formatting"); settings.setValue("justify_text", justify_text); settings.setValue("text_width_enabled", text_width_enabled); settings.setValue("centre_h1", centre_h1); settings.setValue("text_width", text_width); settings.setValue("line_height_p", line_height_p); settings.setValue("line_height_h", line_height_h); settings.setValue("indent_bq", indent_bq); settings.setValue("indent_p", indent_p); settings.setValue("indent_h", indent_h); settings.setValue("indent_l", indent_l); settings.setValue("indent_size", indent_size); settings.setValue("list_symbol", (int)list_symbol); settings.endGroup(); } return true; } bool DocumentStyle::load(QSettings &settings) { switch(settings.value("version", 0).toInt()) { case 0: { if(settings.contains("standard_color")) { standard_font.fromString(settings.value("standard_font").toString()); h1_font.fromString(settings.value("h1_font").toString()); h2_font.fromString(settings.value("h2_font").toString()); h3_font.fromString(settings.value("h3_font").toString()); preformatted_font.fromString(settings.value("preformatted_font").toString()); blockquote_font.fromString(settings.value("standard_font").toString()); background_color = QColor(settings.value("background_color").toString()); standard_color = QColor(settings.value("standard_color").toString()); preformatted_color = QColor(settings.value("preformatted_color").toString()); blockquote_bgcolor = QColor(settings.value("blockquote_color").toString()); blockquote_fgcolor = standard_color; h1_color = QColor(settings.value("h1_color").toString()); h2_color = QColor(settings.value("h2_color").toString()); h3_color = QColor(settings.value("h3_color").toString()); internal_link_color = QColor(settings.value("internal_link_color").toString()); external_link_color = QColor(settings.value("external_link_color").toString()); cross_scheme_link_color = QColor(settings.value("cross_scheme_link_color").toString()); internal_link_prefix = settings.value("internal_link_prefix").toString(); external_link_prefix = settings.value("external_link_prefix").toString(); margin_h = margin_v = settings.value("margins").toDouble(); theme = Theme(settings.value("theme").toInt()); } break; } case 1: { theme = Theme(settings.value("theme", int(theme)).toInt()); background_color = QColor { settings.value("background_color", background_color.name()).toString() }; blockquote_bgcolor = QColor { settings.value("blockquote_color", blockquote_bgcolor.name()).toString() }; margin_h = settings.value("margins_h", 30).toInt(); margin_v = settings.value("margins_v", 55).toInt(); QStringList default_colors = {"black", "darkred", "darkgreen", "darkgoldenrod", "darkblue", "darkmagenta", "darkcyan", "lightgray", "gray", "red", "green", "goldenrod", "lightblue", "magenta", "cyan", "white"}; ansi_colors = settings.value("ansi_colors", default_colors).toStringList(); { settings.beginGroup("Standard"); standard_font.fromString(settings.value("font", standard_font.toString()).toString()); standard_color = QString { settings.value("color", standard_color.name()).toString() }; settings.endGroup(); } { settings.beginGroup("Preformatted"); preformatted_font.fromString(settings.value("font", preformatted_font.toString()).toString()); preformatted_color = QString { settings.value("color", preformatted_color.name()).toString() }; settings.endGroup(); } { settings.beginGroup("H1"); h1_font.fromString(settings.value("font", h1_font.toString()).toString()); h1_color = QString { settings.value("color", h1_color.name()).toString() }; settings.endGroup(); } { settings.beginGroup("H2"); h2_font.fromString(settings.value("font", h2_font.toString()).toString()); h2_color = QString { settings.value("color", h2_color.name()).toString() }; settings.endGroup(); } { settings.beginGroup("H3"); h3_font.fromString(settings.value("font", h3_font.toString()).toString()); h3_color = QString { settings.value("color", h3_color.name()).toString() }; settings.endGroup(); } { settings.beginGroup("Blockquote"); blockquote_font.fromString(settings.value("font", standard_font.toString()).toString()); blockquote_fgcolor = QString { settings.value("color", standard_color.name()).toString() }; settings.endGroup(); } { settings.beginGroup("Link"); internal_link_color = QString { settings.value("color_internal", internal_link_color.name()).toString() }; external_link_color = QString {settings.value("color_external", external_link_color.name()).toString() }; cross_scheme_link_color = QString {settings.value("color_cross_scheme", cross_scheme_link_color.name()).toString() }; internal_link_prefix = settings.value("internal_prefix", internal_link_prefix).toString(); external_link_prefix = settings.value("external_prefix", external_link_prefix).toString(); settings.endGroup(); } { settings.beginGroup("Formatting"); justify_text = settings.value("justify_text", justify_text).toBool(); text_width_enabled = settings.value("text_width_enabled", text_width_enabled).toBool(); centre_h1 = settings.value("centre_h1", centre_h1).toBool(); text_width = settings.value("text_width", text_width).toInt(); line_height_p = settings.value("line_height_p", line_height_p).toDouble(); line_height_h = settings.value("line_height_h", line_height_h).toDouble(); indent_bq = settings.value("indent_bq", indent_bq).toInt(); indent_p = settings.value("indent_p", indent_p).toInt(); indent_h = settings.value("indent_h", indent_h).toInt(); indent_l = settings.value("indent_l", indent_l).toInt(); indent_size = settings.value("indent_size", indent_size).toDouble(); list_symbol = (QTextListFormat::Style)settings.value("list_symbol", list_symbol).toInt(); settings.endGroup(); } } break; default: return false; } return true; } DocumentStyle DocumentStyle::derive(const QUrl &url) const { DocumentStyle themed = *this; // Patch font lists to allow improved emoji display: static QStringList emojiFonts = { "", "", "Apple Color Emoji", "Segoe UI Emoji", "Twitter Color Emoji", "Noto Color Emoji", "JoyPixels", }; DefaultFonts default_fonts; auto const patchup_font = [&default_fonts](QFont & font, bool fixed=false) { // Set the "fallback" font, just to be absolutely sure. // Note the main purpose of this is to avoid emoji fonts // from taking precedence over text fonts. // (fixes *nix default font issues) emojiFonts[1] = fixed ? default_fonts.fixed : default_fonts.regular; // Set the primary font as the preferred font. // We ensure that the font family is available first, // so that we don't get an ugly default font // (fixes Windows' default font problem) QFontDatabase db; if (!db.families().contains(font.family())) { emojiFonts.front() = fixed ? default_fonts.fixed : default_fonts.regular; } else { emojiFonts.front() = font.family(); } // Set emoji fonts if supported and enabled. if (kristall::EMOJIS_SUPPORTED && kristall::globals().options.emojis_enabled) { // Redundant check to make compiler happy... #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) font.setFamilies(emojiFonts); #endif } else { font.setFamily(emojiFonts.front()); } }; patchup_font(themed.h1_font); patchup_font(themed.h2_font); patchup_font(themed.h3_font); patchup_font(themed.standard_font); patchup_font(themed.preformatted_font, true); patchup_font(themed.blockquote_font); if (this->theme == Fixed) return themed; QByteArray hash = QCryptographicHash::hash(url.host().toUtf8(), QCryptographicHash::Md5); std::array items; assert(items.size() == hash.size()); memcpy(items.data(), hash.data(), items.size()); float hue = (items[0] + items[1]) / 510.0; float saturation = items[2] / 255.0; double tmp; switch (this->theme) { case AutoDarkTheme: { themed.background_color = QColor::fromHslF(hue, saturation, 0.25f); themed.standard_color = QColor{0xFF, 0xFF, 0xFF}; themed.h1_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.75); themed.h2_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.75); themed.h3_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.75); themed.external_link_color = QColor::fromHslF(std::modf(hue + 0.25, &tmp), 1.0, 0.75); themed.internal_link_color = themed.external_link_color.lighter(110); themed.cross_scheme_link_color = themed.external_link_color.darker(110); themed.blockquote_bgcolor = themed.background_color.lighter(130); themed.blockquote_fgcolor = QColor{0xEE, 0xEE, 0xEE}; break; } case AutoLightTheme: { themed.background_color = QColor::fromHslF(hue, items[2] / 255.0, 0.85); themed.standard_color = QColor{0x00, 0x00, 0x00}; themed.h1_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.25); themed.h2_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.25); themed.h3_color = QColor::fromHslF(std::modf(hue + 0.5, &tmp), 1.0 - saturation, 0.25); themed.external_link_color = QColor::fromHslF(std::modf(hue + 0.25, &tmp), 1.0, 0.25); themed.internal_link_color = themed.external_link_color.darker(110); themed.cross_scheme_link_color = themed.external_link_color.lighter(110); themed.blockquote_bgcolor = themed.background_color.darker(113); themed.blockquote_fgcolor = QColor{0x40, 0x40, 0x40}; break; } case Fixed: assert(false); } // Same for all themes themed.preformatted_color = themed.standard_color; return themed; } QString DocumentStyle::toStyleSheet() const { QString css; css += QString("p { color: %2; %1 }\n").arg(encodeCssFont (standard_font), standard_color.name()); css += QString("a { color: %2; %1 }\n").arg(encodeCssFont (standard_font), external_link_color.name()); css += QString("pre { color: %2; %1 }\n").arg(encodeCssFont (preformatted_font), preformatted_color.name()); css += QString("h1 { color: %2; %1 }\n").arg(encodeCssFont (h1_font), h1_color.name()); css += QString("h2 { color: %2; %1 }\n").arg(encodeCssFont (h2_font), h2_color.name()); css += QString("h3 { color: %2; %1 }\n").arg(encodeCssFont (h3_font), h3_color.name()); css += QString("blockquote { background: %1; color: %2; %3 }\n") .arg(blockquote_bgcolor.name(), blockquote_fgcolor.name(), encodeCssFont(blockquote_font)); // qDebug() << "CSS → " << css; return css; }