2023-06-22 17:30:43 +02:00
|
|
|
/************************************************************************
|
|
|
|
**
|
|
|
|
** @file vsvgfontreader.cpp
|
|
|
|
** @author Roman Telezhynskyi <dismine(at)gmail.com>
|
|
|
|
** @date 30 5, 2023
|
|
|
|
**
|
|
|
|
** @brief
|
|
|
|
** @copyright
|
|
|
|
** This source code is part of the Valentina project, a pattern making
|
|
|
|
** program, whose allow create and modeling patterns of clothing.
|
|
|
|
** Copyright (C) 2023 Valentina project
|
|
|
|
** <https://gitlab.com/smart-pattern/valentina> All Rights Reserved.
|
|
|
|
**
|
|
|
|
** Valentina 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.
|
|
|
|
**
|
|
|
|
** Valentina 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 Valentina. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
**
|
|
|
|
*************************************************************************/
|
|
|
|
#include "vsvgfontreader.h"
|
|
|
|
#include "../../ifc/exception/vexception.h"
|
|
|
|
#include "../def.h"
|
|
|
|
#include "qpainterpath.h"
|
|
|
|
#include "svgdef.h"
|
|
|
|
#include "vsvgfont.h"
|
|
|
|
#include "vsvgfontengine.h"
|
|
|
|
#include "vsvgpathtokenizer.h"
|
|
|
|
|
|
|
|
#include <QFile>
|
|
|
|
#include <QPainterPath>
|
|
|
|
#include <QtDebug>
|
|
|
|
|
2023-10-07 17:56:39 +02:00
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
|
|
|
|
#include "../compatibility.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
using namespace Qt::Literals::StringLiterals;
|
|
|
|
|
2023-06-22 17:30:43 +02:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
auto ParseFontStyle(const QString &fontStyle) -> SVGFontStyle
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (fontStyle == "normal"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
return SVGFontStyle::Normal;
|
|
|
|
}
|
|
|
|
|
2023-10-07 17:56:39 +02:00
|
|
|
if (fontStyle == "italic"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
return SVGFontStyle::Italic;
|
|
|
|
}
|
|
|
|
|
2023-10-07 17:56:39 +02:00
|
|
|
if (fontStyle == "oblique"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
return SVGFontStyle::Oblique;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SVGFontStyle::Normal;
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
auto ParseFontWeight(const QString &fontWeight) -> SVGFontWeight
|
|
|
|
{
|
|
|
|
if (fontWeight.isEmpty())
|
|
|
|
{
|
|
|
|
return SVGFontWeight::Normal;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ok;
|
|
|
|
int parsedWeight = fontWeight.toInt(&ok);
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
{
|
|
|
|
switch (parsedWeight)
|
|
|
|
{
|
|
|
|
case static_cast<int>(SVGFontWeight::Thin):
|
|
|
|
return SVGFontWeight::Thin;
|
|
|
|
case static_cast<int>(SVGFontWeight::ExtraLight):
|
|
|
|
return SVGFontWeight::ExtraLight;
|
|
|
|
case static_cast<int>(SVGFontWeight::Light):
|
|
|
|
return SVGFontWeight::Light;
|
|
|
|
case static_cast<int>(SVGFontWeight::Medium):
|
|
|
|
return SVGFontWeight::Medium;
|
|
|
|
case static_cast<int>(SVGFontWeight::DemiBold):
|
|
|
|
return SVGFontWeight::DemiBold;
|
|
|
|
case static_cast<int>(SVGFontWeight::Bold):
|
|
|
|
return SVGFontWeight::Bold;
|
|
|
|
case static_cast<int>(SVGFontWeight::ExtraBold):
|
|
|
|
return SVGFontWeight::ExtraBold;
|
|
|
|
case static_cast<int>(SVGFontWeight::Black):
|
|
|
|
return SVGFontWeight::Black;
|
|
|
|
case static_cast<int>(SVGFontWeight::Normal):
|
|
|
|
default:
|
|
|
|
return SVGFontWeight::Normal;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QString fontWeightLower = fontWeight.toLower();
|
2023-10-07 17:56:39 +02:00
|
|
|
if (fontWeightLower == "normal"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
return SVGFontWeight::Normal;
|
|
|
|
}
|
|
|
|
|
2023-10-07 17:56:39 +02:00
|
|
|
if (fontWeightLower == "bold"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
return SVGFontWeight::Bold;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return SVGFontWeight::Normal;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
auto InitFont(const QXmlStreamAttributes &fontAttr) -> VSvgFont
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
const auto hax = fontAttr.value("horiz-adv-x"_L1);
|
2023-06-22 17:30:43 +02:00
|
|
|
qreal horizAdvX = hax.toDouble();
|
|
|
|
|
2023-10-07 17:56:39 +02:00
|
|
|
QString id = fontAttr.value("id"_L1).toString();
|
2023-06-22 17:30:43 +02:00
|
|
|
if (id.isEmpty())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
id = fontAttr.value("xml:id"_L1).toString();
|
2023-06-22 17:30:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
VSvgFont font(horizAdvX);
|
|
|
|
font.SetId(id);
|
|
|
|
return font;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
auto VSvgFontReader::ReadSvgFontHeader(QFile *file) -> VSvgFont
|
|
|
|
{
|
|
|
|
SCASSERT(file != nullptr)
|
|
|
|
|
|
|
|
setDevice(file);
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (readNextStartElement())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (name() == "svg"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
while (readNextStartElement())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (name() == "defs"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
return ReadFontHeader();
|
|
|
|
}
|
|
|
|
|
|
|
|
skipCurrentElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
raiseError(tr("Incorrect file"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const VException &e)
|
|
|
|
{
|
|
|
|
raiseError(e.ErrorMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasError())
|
|
|
|
{
|
|
|
|
qDebug() << "Error parsing SVG font file:" << errorString();
|
|
|
|
}
|
|
|
|
|
|
|
|
clear();
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
auto VSvgFontReader::ReadSvgFont(QFile *file) -> VSvgFontEngine
|
|
|
|
{
|
|
|
|
SCASSERT(file != nullptr)
|
|
|
|
|
|
|
|
setDevice(file);
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (readNextStartElement())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (name() == "svg"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
while (readNextStartElement())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (name() == "defs"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
return ReadFontData();
|
|
|
|
}
|
|
|
|
|
|
|
|
skipCurrentElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
raiseError(tr("Incorrect file"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const VException &e)
|
|
|
|
{
|
|
|
|
raiseError(e.ErrorMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasError())
|
|
|
|
{
|
|
|
|
qDebug() << "Error parsing SVG font file:" << errorString();
|
|
|
|
}
|
|
|
|
|
|
|
|
clear();
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
auto VSvgFontReader::ReadFontHeader() -> VSvgFont
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
AssertRootTag("defs"_L1);
|
2023-06-22 17:30:43 +02:00
|
|
|
|
|
|
|
while (readNextStartElement())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (name() == "font"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
return ReadFontFace();
|
|
|
|
}
|
|
|
|
|
|
|
|
skipCurrentElement();
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
auto VSvgFontReader::ReadFontFace() -> VSvgFont
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
AssertRootTag("font"_L1);
|
2023-06-22 17:30:43 +02:00
|
|
|
|
|
|
|
VSvgFont font = InitFont(attributes());
|
|
|
|
|
|
|
|
while (readNextStartElement())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (name() == "font-face"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
SetFontFace(&font);
|
|
|
|
return font;
|
|
|
|
}
|
|
|
|
|
|
|
|
skipCurrentElement();
|
|
|
|
}
|
|
|
|
|
|
|
|
return font;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
auto VSvgFontReader::ReadFontData() -> VSvgFontEngine
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
AssertRootTag("defs"_L1);
|
2023-06-22 17:30:43 +02:00
|
|
|
|
|
|
|
while (readNextStartElement())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (name() == "font"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
return ReadFont();
|
|
|
|
}
|
|
|
|
|
|
|
|
skipCurrentElement();
|
|
|
|
}
|
|
|
|
|
|
|
|
readElementText();
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
auto VSvgFontReader::ReadFont() -> VSvgFontEngine
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
AssertRootTag("font"_L1);
|
2023-06-22 17:30:43 +02:00
|
|
|
|
|
|
|
VSvgFont font = InitFont(attributes());
|
|
|
|
|
|
|
|
VSvgFontEngine engine;
|
|
|
|
|
|
|
|
while (readNextStartElement())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (name() == "font-face"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
SetFontFace(&font);
|
|
|
|
engine = VSvgFontEngine(font);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
skipCurrentElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (readNextStartElement())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (name() == "missing-glyph"_L1 || name() == "glyph"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
ParseSvgGlyph(&engine, attributes());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
skipCurrentElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
readElementText();
|
|
|
|
|
|
|
|
engine.RecalculateFontSize();
|
|
|
|
engine.RecognizeWritingSystems();
|
|
|
|
|
|
|
|
return engine;
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
void VSvgFontReader::AssertRootTag(const QString &tag) const
|
|
|
|
{
|
|
|
|
if (not(isStartElement() && name() == tag))
|
|
|
|
{
|
|
|
|
throw VException(tr("Unexpected tag %1 in line %2").arg(name().toString()).arg(lineNumber()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
void VSvgFontReader::SetFontFace(VSvgFont *font)
|
|
|
|
{
|
|
|
|
QXmlStreamAttributes fontFaceAttr = attributes();
|
2023-10-07 17:56:39 +02:00
|
|
|
QString fontFamily = fontFaceAttr.value("font-family"_L1).toString();
|
|
|
|
const auto unitsPerEmStr = fontFaceAttr.value("units-per-em"_L1);
|
2023-06-22 17:30:43 +02:00
|
|
|
|
|
|
|
qreal unitsPerEm = unitsPerEmStr.toDouble();
|
|
|
|
if (qFuzzyIsNull(unitsPerEm))
|
|
|
|
{
|
|
|
|
unitsPerEm = 1000;
|
|
|
|
}
|
|
|
|
|
2023-10-07 17:56:39 +02:00
|
|
|
const auto ascentStr = fontFaceAttr.value("ascent"_L1);
|
2023-06-22 17:30:43 +02:00
|
|
|
qreal ascent = ascentStr.toDouble();
|
|
|
|
if (qFuzzyIsNull(ascent))
|
|
|
|
{
|
|
|
|
ascent = 800;
|
|
|
|
}
|
|
|
|
|
2023-10-07 17:56:39 +02:00
|
|
|
const auto descentStr = fontFaceAttr.value("descent"_L1);
|
2023-06-22 17:30:43 +02:00
|
|
|
qreal descent = descentStr.toDouble();
|
|
|
|
if (qFuzzyIsNull(descent))
|
|
|
|
{
|
|
|
|
descent = -200;
|
|
|
|
}
|
|
|
|
|
2023-10-07 17:56:39 +02:00
|
|
|
QString fontStyle = fontFaceAttr.value("font-style"_L1).toString();
|
|
|
|
QString fontWeight = fontFaceAttr.value("font-weight"_L1).toString();
|
2023-06-22 17:30:43 +02:00
|
|
|
QString fontName;
|
|
|
|
|
|
|
|
while (readNextStartElement())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (name() == "font-face-src"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
|
|
|
while (readNextStartElement())
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
if (name() == "font-face-name"_L1)
|
2023-06-22 17:30:43 +02:00
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
fontName = attributes().value("name"_L1).toString();
|
2023-06-22 17:30:43 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
skipCurrentElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
skipCurrentElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
font->SetFamilyName(fontFamily);
|
|
|
|
font->SetName(fontName);
|
|
|
|
font->SetUnitsPerEm(unitsPerEm);
|
|
|
|
font->SetAscent(ascent);
|
|
|
|
font->SetDescent(descent);
|
|
|
|
font->SetStyle(ParseFontStyle(fontStyle));
|
|
|
|
font->SetWeight(ParseFontWeight(fontWeight));
|
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------------------------------
|
|
|
|
void VSvgFontReader::ParseSvgGlyph(VSvgFontEngine *engine, const QXmlStreamAttributes &glyphAttr)
|
|
|
|
{
|
2023-10-07 17:56:39 +02:00
|
|
|
auto uncStr = glyphAttr.value("unicode"_L1);
|
|
|
|
auto havStr = glyphAttr.value("horiz-adv-x"_L1);
|
|
|
|
auto pathStr = glyphAttr.value("d"_L1);
|
2023-06-22 17:30:43 +02:00
|
|
|
|
|
|
|
QChar unicode = (uncStr.isEmpty()) ? u'\0' : uncStr.at(0);
|
|
|
|
qreal havx = (havStr.isEmpty()) ? -1 : havStr.toDouble();
|
|
|
|
QPainterPath path;
|
|
|
|
path.setFillRule(Qt::WindingFill);
|
|
|
|
VSVGPathTokenizer tokenizer(pathStr.toString());
|
|
|
|
tokenizer.SetSinglePath(true); // Do not close subpaths
|
|
|
|
tokenizer.ToPainterPath(path);
|
|
|
|
|
|
|
|
engine->AddGlyph(unicode, path, havx);
|
|
|
|
|
|
|
|
readElementText();
|
|
|
|
}
|