1 /*
2 * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include "KoSvgText.h"
20 #include <SvgUtil.h>
21 #include <KoXmlReader.h>
22 #include <SvgLoadingContext.h>
23 #include <QDebug>
24 #include "kis_dom_utils.h"
25
26 #include <KoColorBackground.h>
27 #include <KoGradientBackground.h>
28 #include <KoVectorPatternBackground.h>
29 #include <KoShapeStroke.h>
30
31 #include <KoSvgTextChunkShape.h>
32 #include <KoSvgTextChunkShapeLayoutInterface.h>
33
34
35 namespace {
36
37 struct TextPropertiesStaticRegistrar {
TextPropertiesStaticRegistrar__anon53aef2660111::TextPropertiesStaticRegistrar38 TextPropertiesStaticRegistrar() {
39 qRegisterMetaType<KoSvgText::AutoValue>("KoSvgText::AutoValue");
40 QMetaType::registerEqualsComparator<KoSvgText::AutoValue>();
41 QMetaType::registerDebugStreamOperator<KoSvgText::AutoValue>();
42
43 qRegisterMetaType<KoSvgText::BackgroundProperty>("KoSvgText::BackgroundProperty");
44 QMetaType::registerEqualsComparator<KoSvgText::BackgroundProperty>();
45 QMetaType::registerDebugStreamOperator<KoSvgText::BackgroundProperty>();
46
47 qRegisterMetaType<KoSvgText::StrokeProperty>("KoSvgText::StrokeProperty");
48 QMetaType::registerEqualsComparator<KoSvgText::StrokeProperty>();
49 QMetaType::registerDebugStreamOperator<KoSvgText::StrokeProperty>();
50
51 qRegisterMetaType<KoSvgText::AssociatedShapeWrapper>("KoSvgText::AssociatedShapeWrapper");
52 }
53 };
54
55 static TextPropertiesStaticRegistrar textPropertiesStaticRegistrar;
56
57 }
58
59 namespace KoSvgText {
60
parseAutoValueX(const QString & value,const SvgLoadingContext & context,const QString & autoKeyword)61 AutoValue parseAutoValueX(const QString &value, const SvgLoadingContext &context, const QString &autoKeyword)
62 {
63 return value == autoKeyword ? AutoValue() : SvgUtil::parseUnitX(context.currentGC(), value);
64 }
65
parseAutoValueY(const QString & value,const SvgLoadingContext & context,const QString & autoKeyword)66 AutoValue parseAutoValueY(const QString &value, const SvgLoadingContext &context, const QString &autoKeyword)
67 {
68 return value == autoKeyword ? AutoValue() : SvgUtil::parseUnitY(context.currentGC(), value);
69 }
70
parseAutoValueXY(const QString & value,const SvgLoadingContext & context,const QString & autoKeyword)71 AutoValue parseAutoValueXY(const QString &value, const SvgLoadingContext &context, const QString &autoKeyword)
72 {
73 return value == autoKeyword ? AutoValue() : SvgUtil::parseUnitXY(context.currentGC(), value);
74 }
75
parseAutoValueAngular(const QString & value,const SvgLoadingContext & context,const QString & autoKeyword)76 AutoValue parseAutoValueAngular(const QString &value, const SvgLoadingContext &context, const QString &autoKeyword)
77 {
78 return value == autoKeyword ? AutoValue() : SvgUtil::parseUnitAngular(context.currentGC(), value);
79 }
80
parseWritingMode(const QString & value)81 WritingMode parseWritingMode(const QString &value) {
82 return (value == "tb-rl" || value == "tb") ? TopToBottom :
83 (value == "rl-tb" || value == "rl") ? RightToLeft :
84 LeftToRight;
85 }
86
parseDirection(const QString & value)87 Direction parseDirection(const QString &value) {
88 return value == "rtl" ? DirectionRightToLeft : DirectionLeftToRight;
89 }
90
parseUnicodeBidi(const QString & value)91 UnicodeBidi parseUnicodeBidi(const QString &value)
92 {
93 return value == "embed" ? BidiEmbed :
94 value == "bidi-override" ? BidiOverride :
95 BidiNormal;
96 }
97
parseTextAnchor(const QString & value)98 TextAnchor parseTextAnchor(const QString &value)
99 {
100 return value == "middle" ? AnchorMiddle :
101 value == "end" ? AnchorEnd :
102 AnchorStart;
103 }
104
parseDominantBaseline(const QString & value)105 DominantBaseline parseDominantBaseline(const QString &value)
106 {
107 return value == "use-script" ? DominantBaselineUseScript :
108 value == "no-change" ? DominantBaselineNoChange:
109 value == "reset-size" ? DominantBaselineResetSize:
110 value == "ideographic" ? DominantBaselineIdeographic :
111 value == "alphabetic" ? DominantBaselineAlphabetic :
112 value == "hanging" ? DominantBaselineHanging :
113 value == "mathematical" ? DominantBaselineMathematical :
114 value == "central" ? DominantBaselineCentral :
115 value == "middle" ? DominantBaselineMiddle :
116 value == "text-after-edge" ? DominantBaselineTextAfterEdge :
117 value == "text-before-edge" ? DominantBaselineTextBeforeEdge :
118 DominantBaselineAuto;
119 }
120
parseAlignmentBaseline(const QString & value)121 AlignmentBaseline parseAlignmentBaseline(const QString &value)
122 {
123 return value == "baseline" ? AlignmentBaselineDominant :
124 value == "ideographic" ? AlignmentBaselineIdeographic :
125 value == "alphabetic" ? AlignmentBaselineAlphabetic :
126 value == "hanging" ? AlignmentBaselineHanging :
127 value == "mathematical" ? AlignmentBaselineMathematical :
128 value == "central" ? AlignmentBaselineCentral :
129 value == "middle" ? AlignmentBaselineMiddle :
130 (value == "text-after-edge" || value == "after-edge") ? AlignmentBaselineTextAfterEdge :
131 (value == "text-before-edge" || value == "before-edge") ? AlignmentBaselineTextBeforeEdge :
132 AlignmentBaselineAuto;
133 }
134
parseBaselineShiftMode(const QString & value)135 BaselineShiftMode parseBaselineShiftMode(const QString &value)
136 {
137 return value == "baseline" ? ShiftNone :
138 value == "sub" ? ShiftSub :
139 value == "super" ? ShiftSuper :
140 ShiftPercentage;
141 }
142
parseLengthAdjust(const QString & value)143 LengthAdjust parseLengthAdjust(const QString &value)
144 {
145 return value == "spacingAndGlyphs" ? LengthAdjustSpacingAndGlyphs : LengthAdjustSpacing;
146 }
147
writeAutoValue(const AutoValue & value,const QString & autoKeyword)148 QString writeAutoValue(const AutoValue &value, const QString &autoKeyword)
149 {
150 return value.isAuto ? autoKeyword : KisDomUtils::toString(value.customValue);
151 }
152
writeWritingMode(WritingMode value)153 QString writeWritingMode(WritingMode value)
154 {
155 return value == TopToBottom ? "tb" : value == RightToLeft ? "rl" : "lr";
156 }
157
writeDirection(Direction value)158 QString writeDirection(Direction value)
159 {
160 return value == DirectionRightToLeft ? "rtl" : "ltr";
161 }
162
writeUnicodeBidi(UnicodeBidi value)163 QString writeUnicodeBidi(UnicodeBidi value)
164 {
165 return value == BidiEmbed ? "embed" : value == BidiOverride ? "bidi-override" : "normal";
166 }
167
writeTextAnchor(TextAnchor value)168 QString writeTextAnchor(TextAnchor value)
169 {
170 return value == AnchorEnd ? "end" : value == AnchorMiddle ? "middle" : "start";
171 }
172
writeDominantBaseline(DominantBaseline value)173 QString writeDominantBaseline(DominantBaseline value)
174 {
175 return value == DominantBaselineUseScript ? "use-script" :
176 value == DominantBaselineNoChange ? "no-change" :
177 value == DominantBaselineResetSize ? "reset-size" :
178 value == DominantBaselineIdeographic ? "ideographic" :
179 value == DominantBaselineAlphabetic ? "alphabetic" :
180 value == DominantBaselineHanging ? "hanging" :
181 value == DominantBaselineMathematical ? "mathematical" :
182 value == DominantBaselineCentral ? "central" :
183 value == DominantBaselineMiddle ? "middle" :
184 value == DominantBaselineTextAfterEdge ? "text-after-edge" :
185 value == DominantBaselineTextBeforeEdge ? "text-before-edge" :
186 "auto";
187 }
188
writeAlignmentBaseline(AlignmentBaseline value)189 QString writeAlignmentBaseline(AlignmentBaseline value)
190 {
191 return value == AlignmentBaselineDominant ? "baseline" :
192 value == AlignmentBaselineIdeographic ? "ideographic" :
193 value == AlignmentBaselineAlphabetic ? "alphabetic" :
194 value == AlignmentBaselineHanging ? "hanging" :
195 value == AlignmentBaselineMathematical ? "mathematical" :
196 value == AlignmentBaselineCentral ? "central" :
197 value == AlignmentBaselineMiddle ? "middle" :
198 value == AlignmentBaselineTextAfterEdge ? "text-after-edge" :
199 value == AlignmentBaselineTextBeforeEdge ? "text-before-edge" :
200 "auto";
201 }
202
writeBaselineShiftMode(BaselineShiftMode value,qreal portion)203 QString writeBaselineShiftMode(BaselineShiftMode value, qreal portion)
204 {
205 return value == ShiftNone ? "baseline" :
206 value == ShiftSub ? "sub" :
207 value == ShiftSuper ? "super" :
208 SvgUtil::toPercentage(portion);
209 }
210
writeLengthAdjust(LengthAdjust value)211 QString writeLengthAdjust(LengthAdjust value)
212 {
213 return value == LengthAdjustSpacingAndGlyphs ? "spacingAndGlyphs" : "spacing";
214 }
215
operator <<(QDebug dbg,const KoSvgText::AutoValue & value)216 QDebug operator<<(QDebug dbg, const KoSvgText::AutoValue &value)
217 {
218 dbg.nospace() << (value.isAuto ? "auto" : QString::number(value.customValue));
219 return dbg.space();
220 }
221
mergeInParentTransformation(const CharTransformation & t)222 void CharTransformation::mergeInParentTransformation(const CharTransformation &t)
223 {
224 if (!xPos && t.xPos) {
225 xPos = *t.xPos;
226 }
227
228 if (!yPos && t.yPos) {
229 yPos = *t.yPos;
230 }
231
232 if (!dxPos && t.dxPos) {
233 dxPos = *t.dxPos;
234 }
235
236 if (!dyPos && t.dyPos) {
237 dyPos = *t.dyPos;
238 }
239
240 if (!rotate && t.rotate) {
241 rotate = *t.rotate;
242 }
243 }
244
isNull() const245 bool CharTransformation::isNull() const
246 {
247 return !xPos && !yPos && !dxPos && !dyPos && !rotate;
248 }
249
startsNewChunk() const250 bool CharTransformation::startsNewChunk() const
251 {
252 return xPos || yPos;
253 }
254
hasRelativeOffset() const255 bool CharTransformation::hasRelativeOffset() const
256 {
257 return dxPos || dyPos;
258 }
259
absolutePos() const260 QPointF CharTransformation::absolutePos() const
261 {
262 QPointF result;
263
264 if (xPos) {
265 result.rx() = *xPos;
266 }
267
268 if (yPos) {
269 result.ry() = *yPos;
270 }
271
272 return result;
273 }
274
relativeOffset() const275 QPointF CharTransformation::relativeOffset() const
276 {
277 QPointF result;
278
279 if (dxPos) {
280 result.rx() = *dxPos;
281 }
282
283 if (dyPos) {
284 result.ry() = *dyPos;
285 }
286
287 return result;
288 }
289
operator ==(const CharTransformation & other) const290 bool CharTransformation::operator==(const CharTransformation &other) const {
291 return
292 xPos == other.xPos && yPos == other.yPos &&
293 dxPos == other.dxPos && dyPos == other.dyPos &&
294 rotate == other.rotate;
295 }
296
297 namespace {
addSeparator(QDebug dbg,bool hasPreviousContent)298 QDebug addSeparator(QDebug dbg, bool hasPreviousContent) {
299 return hasPreviousContent ? (dbg.nospace() << "; ") : dbg;
300 }
301 }
302
operator <<(QDebug dbg,const CharTransformation & t)303 QDebug operator<<(QDebug dbg, const CharTransformation &t)
304 {
305 dbg.nospace() << "CharTransformation(";
306
307 bool hasContent = false;
308
309 if (t.xPos) {
310 dbg.nospace() << "xPos = " << *t.xPos;
311 hasContent = true;
312 }
313
314 if (t.yPos) {
315 dbg = addSeparator(dbg, hasContent);
316 dbg.nospace() << "yPos = " << *t.yPos;
317 hasContent = true;
318 }
319
320 if (t.dxPos) {
321 dbg = addSeparator(dbg, hasContent);
322 dbg.nospace() << "dxPos = " << *t.dxPos;
323 hasContent = true;
324 }
325
326 if (t.dyPos) {
327 dbg = addSeparator(dbg, hasContent);
328 dbg.nospace() << "dyPos = " << *t.dyPos;
329 hasContent = true;
330 }
331
332 if (t.rotate) {
333 dbg = addSeparator(dbg, hasContent);
334 dbg.nospace() << "rotate = " << *t.rotate;
335 hasContent = true;
336 }
337
338 dbg.nospace() << ")";
339 return dbg.space();
340 }
341
342
343
operator <<(QDebug dbg,const BackgroundProperty & prop)344 QDebug operator<<(QDebug dbg, const BackgroundProperty &prop)
345 {
346 dbg.nospace() << "BackgroundProperty(";
347
348 dbg.nospace() << prop.property.data();
349
350 if (KoColorBackground *fill = dynamic_cast<KoColorBackground*>(prop.property.data())) {
351 dbg.nospace() << ", color, " << fill->color();
352 }
353
354 if (KoGradientBackground *fill = dynamic_cast<KoGradientBackground*>(prop.property.data())) {
355 dbg.nospace() << ", gradient, " << fill->gradient();
356 }
357
358 if (KoVectorPatternBackground *fill = dynamic_cast<KoVectorPatternBackground*>(prop.property.data())) {
359 dbg.nospace() << ", pattern, num shapes: " << fill->shapes().size();
360 }
361
362 dbg.nospace() << ")";
363 return dbg.space();
364 }
365
operator <<(QDebug dbg,const StrokeProperty & prop)366 QDebug operator<<(QDebug dbg, const StrokeProperty &prop)
367 {
368 dbg.nospace() << "StrokeProperty(";
369
370 dbg.nospace() << prop.property.data();
371
372 if (KoShapeStroke *stroke = dynamic_cast<KoShapeStroke*>(prop.property.data())) {
373 dbg.nospace() << ", " << stroke->resultLinePen();
374 }
375
376 dbg.nospace() << ")";
377 return dbg.space();
378 }
379
AssociatedShapeWrapper()380 AssociatedShapeWrapper::AssociatedShapeWrapper()
381 {
382 }
383
AssociatedShapeWrapper(KoSvgTextChunkShape * shape)384 AssociatedShapeWrapper::AssociatedShapeWrapper(KoSvgTextChunkShape *shape)
385 : m_shape(shape)
386 {
387 if (m_shape) {
388 m_shape->addShapeChangeListener(this);
389 }
390 }
391
AssociatedShapeWrapper(const AssociatedShapeWrapper & rhs)392 AssociatedShapeWrapper::AssociatedShapeWrapper(const AssociatedShapeWrapper &rhs)
393 : AssociatedShapeWrapper(rhs.m_shape)
394 {
395 }
396
operator =(const AssociatedShapeWrapper & rhs)397 AssociatedShapeWrapper &AssociatedShapeWrapper::operator=(const AssociatedShapeWrapper &rhs)
398 {
399 if (m_shape) {
400 m_shape->removeShapeChangeListener(this);
401 m_shape = 0;
402 }
403
404 m_shape = rhs.m_shape;
405
406 if (m_shape) {
407 m_shape->addShapeChangeListener(this);
408 }
409
410 return *this;
411 }
412
isValid() const413 bool AssociatedShapeWrapper::isValid() const
414 {
415 return m_shape;
416 }
417
notifyShapeChanged(KoShape::ChangeType type,KoShape * shape)418 void AssociatedShapeWrapper::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape)
419 {
420 KIS_SAFE_ASSERT_RECOVER_RETURN(shape == m_shape);
421
422 if (type == KoShape::Deleted) {
423 m_shape = 0;
424 }
425 }
426
addCharacterRect(const QRectF & rect)427 void AssociatedShapeWrapper::addCharacterRect(const QRectF &rect)
428 {
429 if (m_shape) {
430 m_shape->layoutInterface()->addAssociatedOutline(rect);
431 }
432 }
433
434 }
435
436
437