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