Krita Source Code Documentation
Loading...
Searching...
No Matches
CssQmlUnitConverter.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2025 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
7#include <QVariant>
8#include <KoUnit.h>
9#include <KoSvgText.h>
10#include <KoSvgTextProperties.h>
12#include <KLocalizedString>
13
15 QMap<int, int> dataUnitMap;
16
17 qreal dpi{72.0};
18 qreal dataMultiplier{1.0};
19 qreal dataValue{0.0};
20 int dataUnit{-1};
21 int userUnit{-1};
22 qreal percentageReference {12.0};
23
25
26 // fontmetrics
28 int fontLineGap = 0;
30};
31
32// Map of absolute values.
33// by making this a map, it always stays order in the order of KoUnit::type.
41
42const qreal koUnitFactor = 1/72.0;
43
45 : QObject(parent)
46 , d(new Private)
47{
48 d->absoluteUnitConverter = KoUnit(KoUnit::Point, d->dpi);
51}
52
56
57void CssQmlUnitConverter::setDataUnitMap(const QVariantList &unitMap)
58{
59 QMap<int, int> map;
60 Q_FOREACH(QVariant var, unitMap) {
61 QVariantMap m = var.toMap();
62 bool ok = false;
63 const int user = m.value("user").toInt(&ok);
64 const int data = m.value("data").toInt(&ok);
65 if (!ok) {
66 qWarning() << Q_FUNC_INFO << "unitMap has wrong format";
67 return;
68 }
69 map.insert(user, data);
70 }
71
72 if (d->dataUnitMap != map) {
73 d->dataUnitMap = map;
75 }
76}
77
78void CssQmlUnitConverter::setFontMetricsFromTextPropertiesModel(KoSvgTextPropertiesModel *textPropertiesModel, bool isFontSize, bool isLineHeight)
79{
81 int parentLineHeight = -1;
82 if (textPropertiesModel) {
83 KoSvgTextPropertyData data = textPropertiesModel->textData.get();
84 // remove fontsize or lineheight for those specific properties, so the calculation is normal.
85 if (isFontSize) {
88 } else {
91 }
92 if (isLineHeight) {
93 KoSvgText::FontMetrics m = main.metrics(true);
94 parentLineHeight = m.ascender-m.descender + m.lineGap;
95 }
96 }
97 KoCSSFontInfo info = main.cssFontInfo();
98 if (info.size < 0) return;
99 if (d->fontInfo == info) {
100 return;
101 }
102 d->fontInfo = main.cssFontInfo();
103 d->metrics = main.metrics(false);
104 d->fontLineGap = d->metrics.lineGap;
105 if (isLineHeight) {
106 d->metrics = main.applyLineHeight(d->metrics);
107 } else {
108 d->metrics.lineGap = parentLineHeight - (d->metrics.ascender-d->metrics.descender);
109 }
110}
111
113{
114 const qreal multiplier = d->fontInfo.size / d->metrics.fontSize;
115 qreal normalLineHeight = (d->metrics.ascender - d->metrics.descender + d->fontLineGap)*multiplier;
116 setDataValue(convertToRelativeValue(normalLineHeight, UserUnits(d->dataUnitMap.key(d->dataUnit))));
117}
118
119void CssQmlUnitConverter::setDataValueAndUnit(const qreal value, const int unit)
120{
121 const bool updateVal = !qFuzzyCompare(d->dataValue, value);
122 const bool updateUnit = d->dataUnit != unit;
123 if (!updateVal && !updateUnit) {
124 return;
125 }
126
127 d->dataUnit = unit;
128 d->dataValue = value;
129 d->userUnit = d->dataUnitMap.key(d->dataUnit, UserUnits::Pt);
130 if (koUnitMap.values().contains(UserUnits(d->userUnit))) {
131 d->absoluteUnitConverter = KoUnit(koUnitMap.key(UserUnits(d->userUnit)), d->dpi * koUnitFactor);
132 }
133
134 emit dataUnitChanged();
135 emit userUnitChanged();
136 emit dataValueChanged();
137 emit userValueChanged();
138}
139
141{
142 return d->dpi;
143}
144
146{
147 if (qFuzzyCompare(d->dpi, newDpi))
148 return;
149 d->dpi = newDpi;
150 d->absoluteUnitConverter.setFactor(newDpi * koUnitFactor);
151 emit dpiChanged();
152 emit userValueChanged();
153}
154
156{
157 return d->dataMultiplier;
158}
159
160void CssQmlUnitConverter::setDataMultiplier(qreal newDataMultiplier)
161{
162 if (qFuzzyCompare(d->dataMultiplier, newDataMultiplier))
163 return;
164 d->dataMultiplier = newDataMultiplier;
166}
167
169{
170 return d->dataValue;
171}
172
173void CssQmlUnitConverter::setDataValue(qreal newDataValue)
174{
175 if (qFuzzyCompare(d->dataValue, newDataValue))
176 return;
177 d->dataValue = newDataValue;
178 emit dataValueChanged();
179 emit userValueChanged();
180}
181
183{
184 return d->dataUnit;
185}
186
188{
189 if (d->dataUnit == newDataUnit)
190 return;
191 if (d->dataUnitMap.key(d->dataUnit) == d->userUnit)
192 return;
193 setUserUnit(d->dataUnitMap.key(d->dataUnit));
194}
195
197{
198 const bool isAbsolute = koUnitMap.values().contains(UserUnits(d->userUnit));
199 return (isAbsolute? d->absoluteUnitConverter.toUserValue(d->dataValue): d->dataValue) * d->dataMultiplier;
200}
201
202void CssQmlUnitConverter::setUserValue(qreal newUserValue)
203{
204 if (qFuzzyCompare(userValue(), newUserValue))
205 return;
206 const bool isAbsolute = koUnitMap.values().contains(UserUnits(d->userUnit));
207 d->dataValue = isAbsolute? d->absoluteUnitConverter.fromUserValue(newUserValue/d->dataMultiplier): (newUserValue/d->dataMultiplier);
208 emit userValueChanged();
209 emit dataValueChanged();
210}
211
213{
214 return d->userUnit;
215}
216
218{
219 if (d->userUnit == newUserUnit)
220 return;
221 UserUnits newType = UserUnits(newUserUnit);
222 const bool newIsAbsolute = koUnitMap.values().contains(newType);
223 const bool oldIsAbsolute = koUnitMap.values().contains(UserUnits(d->userUnit));
224
225 const qreal currentAbsoluteValue = oldIsAbsolute?
226 d->absoluteUnitConverter.fromUserValue(d->dataValue):
227 convertFromRelativeValue(d->dataValue, UserUnits(d->userUnit));
228
229 if (newIsAbsolute && oldIsAbsolute) {
230 KoUnit newUnitConverter(koUnitMap.key(newType), d->dpi * koUnitFactor);
231 d->absoluteUnitConverter = newUnitConverter;
232 emit userValueChanged();
233 } else if (newIsAbsolute) {
234 KoUnit newUnitConverter(koUnitMap.key(newType), d->dpi* koUnitFactor);
235 d->absoluteUnitConverter = newUnitConverter;
236 d->dataUnit = d->dataUnitMap.value(UserUnits::Pt);
237 emit dataUnitChanged();
238 setDataValue(currentAbsoluteValue);
239 } else { // old value was absolute, and new value is relative, or both values are relative.
240 d->dataUnit = d->dataUnitMap.value(newUserUnit);
241 emit dataUnitChanged();
242 setDataValue(convertToRelativeValue(currentAbsoluteValue, newType));
243 }
244
245 d->userUnit = newUserUnit;
246 emit userUnitChanged();
247}
248
250{
251 QVariantList userUnitModel;
252
253 const QString descriptionKey = "description";
254 const QString valueKey = "value";
255 Q_FOREACH(const int key, d->dataUnitMap.keys()) {
256 UserUnits keyUnit = UserUnits(key);
257
258 if (keyUnit == Pt) {
259 Q_FOREACH(KoUnit::Type koUnitKey, koUnitMap.keys()) {
260 QVariantMap unit;
261 unit.insert(descriptionKey, KoUnit::unitDescription(koUnitKey));
262 unit.insert(valueKey, koUnitMap.value(koUnitKey));
263 userUnitModel.append(unit);
264 }
265 } else {
266 QVariantMap unit;
267 if (keyUnit == Em) {
268 unit.insert(descriptionKey, i18nc("@label:inlistbox", "Font Size (em)"));
269 } else if (keyUnit == Ex) {
270 unit.insert(descriptionKey, i18nc("@label:inlistbox", "X Height (ex)"));
271 } else if (keyUnit == Cap) {
272 unit.insert(descriptionKey, i18nc("@label:inlistbox", "Cap Height (cap)"));
273 } else if (keyUnit == Ch) {
274 unit.insert(descriptionKey, i18nc("@label:inlistbox", "Proportional Advance (ch)"));
275 } else if (keyUnit == Ic) {
276 unit.insert(descriptionKey, i18nc("@label:inlistbox", "Ideographic Advance (ic)"));
277 } else if (keyUnit == Lh) {
278 unit.insert(descriptionKey, i18nc("@label:inlistbox", "Line Height (lh)"));
279 } else if (keyUnit == Spaces) {
280 unit.insert(descriptionKey, i18nc("@label:inlistbox", "Spaces (Sp)"));
281 } else if (keyUnit == Lines) {
282 unit.insert(descriptionKey, i18nc("@label:inlistbox", "Lines (Ln)"));
283 } else if (keyUnit == Percentage) {
284 unit.insert(descriptionKey, i18nc("@label:inlistbox", "Percentage (%)"));
285 }
286 unit.insert(valueKey, keyUnit);
287 userUnitModel.append(unit);
288 }
289 }
290
291 return userUnitModel;
292}
293
295{
296 UserUnits unit = UserUnits(d->userUnit);
297 const bool isAbsolute = koUnitMap.values().contains(unit);
298 if (isAbsolute) {
299 return d->absoluteUnitConverter.symbol();
300 }
301 if (unit == Em) {
302 return QString("em");
303 } else if (unit == Ex) {
304 return QString("ex");
305 } else if (unit == Cap) {
306 return QString("cap");
307 } else if (unit == Ch) {
308 return QString("ch");
309 } else if (unit == Ic) {
310 return QString("ic");
311 } else if (unit == Lh) {
312 return QString("lh");
313 } else if (unit == Lines) {
314 return QString("Ln");
315 } else if (unit == Spaces) {
316 return QString("Sp");
317 } else if (unit == Percentage) {
318 return QString("%");
319 }
320 return QString();
321}
322
324{
325 return d->percentageReference;
326}
327
328void CssQmlUnitConverter::setPercentageReference(qreal newPercentageReference)
329{
330 if (qFuzzyCompare(d->percentageReference, newPercentageReference))
331 return;
332 d->percentageReference = newPercentageReference;
334}
335
336qreal metricsMultiplier(const CssQmlUnitConverter::UserUnits type, const KoSvgText::FontMetrics metrics, const qreal fontSize, const qreal percentageReference) {
337 const qreal multiplier = fontSize / metrics.fontSize;
338 if (type == CssQmlUnitConverter::Em) {
339 return fontSize;
340 } else if (type == CssQmlUnitConverter::Ex) {
341 return metrics.xHeight * multiplier;
342 } else if (type == CssQmlUnitConverter::Cap) {
343 return metrics.capHeight * multiplier;
344 } else if (type == CssQmlUnitConverter::Ch) {
345 return metrics.zeroAdvance * multiplier;
346 } else if (type == CssQmlUnitConverter::Ic) {
347 return metrics.ideographicAdvance * multiplier;
348 } else if (type == CssQmlUnitConverter::Lh) {
349 return (metrics.ascender - metrics.descender + metrics.lineGap) * multiplier;
350 } else if (type == CssQmlUnitConverter::Lines) {
351 return (metrics.ascender - metrics.descender) * multiplier;
352 } else if (type == CssQmlUnitConverter::Spaces) {
353 return metrics.spaceAdvance * multiplier;
354 } else if (type == CssQmlUnitConverter::Percentage) {
355 return percentageReference;
356 }
357 return 1.0;
358}
359
361{
362 return value / metricsMultiplier(type, d->metrics, d->fontInfo.size, d->percentageReference);
363}
364
366{
367 return value * metricsMultiplier(type, d->metrics, d->fontInfo.size, d->percentageReference);
368}
const qreal koUnitFactor
const QMap< KoUnit::Type, CssQmlUnitConverter::UserUnits > koUnitMap
qreal metricsMultiplier(const CssQmlUnitConverter::UserUnits type, const KoSvgText::FontMetrics metrics, const qreal fontSize, const qreal percentageReference)
float value(const T *src, size_t ch)
QString symbol
The symbol.
void setPercentageReference(qreal newPercentageReference)
@ Ic
average full-width advance
@ Lines
Used by lineHeight.
@ Px
Pixels, dpi relative.
@ Ch
average proportional advance
Q_INVOKABLE void setFontMetricsFromTextPropertiesModel(KoSvgTextPropertiesModel *textPropertiesModel, bool isFontSize=false, bool isLineHeight=false)
setFontMetricsFromTextPropertiesModel Set the current font metrics from the text properties model.
qreal convertToRelativeValue(const qreal value, const UserUnits type) const
const QScopedPointer< Private > d
qreal percentageReference
value to represent 100%
void setDataMultiplier(qreal newDataMultiplier)
QVariantList userUnitModel
A model for the available user units.
void dataMultiplierChanged()
void setDataUnit(int newDataUnit)
void setUserUnit(int newUserUnit)
qreal convertFromRelativeValue(const qreal value, const UserUnits type) const
void setDpi(qreal newDpi)
CssQmlUnitConverter(QObject *parent=nullptr)
void setUserValue(qreal newUserValue)
qreal dpi
The DPI used to calculate pixel to physical properties.
void setDataValue(qreal newDataValue)
void percentageReferenceChanged()
Q_INVOKABLE void setDataUnitMap(const QVariantList &unitMap)
setDataUnitMap
Q_INVOKABLE void setFromNormalLineHeight()
setFromNormalLineHeight set the current value from line-height:normal metrics.
qreal userValue
userValue and userUnit represent the user-visible data. This can include synthetic units like px and ...
qreal dataMultiplier
Data multiplier is used if for some reason the user-value needs to be multiplied by a certain factor.
Q_INVOKABLE void setDataValueAndUnit(const qreal value, const int unit)
setDataValueAndUnit set data unit and value in one go.
qreal dataValue
dataValue and dataUnit represent the data as used by the text properties.
The KoSvgTextPropertiesModel class.
lager::cursor< KoSvgTextPropertyData > textData
static const KoSvgTextProperties & defaultProperties()
KoCSSFontInfo cssFontInfo() const
cssFontInfo
KoSvgText::FontMetrics metrics(const bool withResolvedLineHeight=true) const
metrics Return the metrics of the first available font.
void inheritFrom(const KoSvgTextProperties &parentProperties, bool resolve=false)
static QString unitDescription(KoUnit::Type type)
Get the description string of the given unit.
Definition KoUnit.cpp:32
@ Point
Postscript point, 1/72th of an Inco.
Definition KoUnit.h:76
@ Centimeter
Definition KoUnit.h:78
@ Millimeter
Definition KoUnit.h:75
@ Inch
Definition KoUnit.h:77
@ Pixel
Definition KoUnit.h:82
static bool qFuzzyCompare(half p1, half p2)
int main(int argc, char **argv)
Definition main.cpp:26
qreal percentageReference
12.0 pt is the default we use for font-size, which most css text properties' % resolve against.
The KoCSSFontInfo class Convenience struct to make it easier to use KoFontRegistry....
The KoSvgTextPropertyData struct.
KoSvgTextProperties commonProperties
The properties common between all the selected text.
KoSvgTextProperties inheritedProperties
The properties that are inherited, so that widgets may be set correctly.
The FontMetrics class A class to keep track of a variety of font metrics. Note that values are in Fre...
Definition KoSvgText.h:327
qint32 xHeight
height of X, defaults to 0.5 fontsize.
Definition KoSvgText.h:334
qint32 lineGap
additional linegap between consecutive lines.
Definition KoSvgText.h:341
qint32 zeroAdvance
Advance of the character '0', CSS Unit 'ch', defaults to 0.5 em in horizontal and 1....
Definition KoSvgText.h:330
qint32 ideographicAdvance
Advance of the character '水' (U+6C34), CSS Unit ic, defaults to 1 em.
Definition KoSvgText.h:332
qint32 fontSize
Currently set size, CSS unit 'em'.
Definition KoSvgText.h:329
qint32 descender
distance for origin to bottom.
Definition KoSvgText.h:340
qint32 ascender
distance from origin to top.
Definition KoSvgText.h:339
qint32 capHeight
Height of capital letters, defaults to ascender.
Definition KoSvgText.h:335
qint32 spaceAdvance
Advance of the character ' ', used by tabs.
Definition KoSvgText.h:331