Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
StyledTextIO_RTF.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <bitset>
7#include <cctype>
8#include <climits>
9#include <cstdio> // for a couple sprintf() calls - could pretty easily be avoided
10
12#if qStroika_Foundation_Common_Platform_Windows
13#include "Stroika/Foundation/Characters/Platform/Windows/CodePage.h"
14#endif
18#include "Stroika/Foundation/DataExchange/BadFormatException.h"
19#if qStroika_Foundation_Common_Platform_Windows
20#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
21#endif
22
23#include "Stroika/Frameworks/Led/Config.h"
24
25#include "Stroika/Frameworks/Led/Support.h"
26
27#include "StyledTextIO_RTF.h"
28
29#define qUseCompiledSetHack true
30
31using namespace Stroika::Foundation;
33
34using namespace Stroika::Frameworks;
35using namespace Stroika::Frameworks::Led;
36using namespace Stroika::Frameworks::Led::StyledTextIO;
37
38#if qStroika_Foundation_Common_Platform_Windows
40#endif
41
42namespace {
43 inline int ConvertReadSingleHexDigit_ (char digit)
44 {
45 if (isupper (digit)) {
46 digit = static_cast<char> (tolower (digit));
47 }
48 if (isdigit (digit)) {
49 return digit - '0';
50 }
51 else if (islower (digit)) {
52 return 10 + (digit - 'a');
53 }
54 else {
55 Execution::Throw (DataExchange::BadFormatException::kThe);
56 Assert (false);
57 return 0; // not reached
58 }
59 }
60 inline char ConvertWriteSingleHexDigit_ (int numZeroToFifteen)
61 {
62 Require (numZeroToFifteen >= 0);
63 Require (numZeroToFifteen <= 15);
64 if (numZeroToFifteen < 10) {
65 return static_cast<char> ('0' + numZeroToFifteen);
66 }
67 else {
68 return static_cast<char> ('a' + (numZeroToFifteen - 10));
69 }
70 }
71
72 // RTF / LineSpacing support
73 inline static LineSpacing mkLineSpacing_From_RTFValues_ (CoordinateType sl, bool multi)
74 {
75 LineSpacing result; // defaults to single line...
76 if (sl != 1000) {
77 if (multi) {
78 /*
79 * This / 12 is total guesswork. The RTF 1.5 Spec is TOTALLY VAGUE. Much of this was just guestimated
80 * and infered from the Win32 Docs on PARAFORMAT2, plus the trial and error - which yielded that
81 * dividing by 12 got the right answer!
82 * -- LGP 2000/06/12
83 */
84 if (sl < 0 or sl > 2000) {
85 sl = 240; // will work out to single line - don't let bogus values in the RTF file make trouble for us...
86 }
87 result = LineSpacing (LineSpacing::eExactLinesSpacing, sl / 12);
88 }
89 else {
90 if (sl < 0) {
91 result = LineSpacing (LineSpacing::eExactTWIPSSpacing, TWIPS (-sl));
92 }
93 else {
94 result = LineSpacing (LineSpacing::eAtLeastTWIPSSpacing, TWIPS (sl));
95 }
96 }
97 }
98 return result;
99 }
100 inline static void mkRTFValues_From_LineSpacing (LineSpacing inLS, CoordinateType* sl, bool* multi)
101 {
102 const int kOneLinesWorth = 240;
103 switch (inLS.fRule) {
104 case LineSpacing::eOnePointFiveSpace:
105 *sl = static_cast<CoordinateType> (kOneLinesWorth * 1.5);
106 *multi = true;
107 break;
108 case LineSpacing::eDoubleSpace:
109 *sl = kOneLinesWorth * 2;
110 *multi = true;
111 break;
112 case LineSpacing::eAtLeastTWIPSSpacing:
113 *sl = inLS.fArg;
114 *multi = false;
115 break;
116 case LineSpacing::eExactTWIPSSpacing:
117 *sl = -static_cast<CoordinateType> (inLS.fArg);
118 *multi = false;
119 break;
120 case LineSpacing::eExactLinesSpacing:
121 *sl = inLS.fArg * 12;
122 *multi = true;
123 break;
124
125 default: // Treat as Single space
126 case LineSpacing::eSingleSpace:
127 *sl = 1000;
128 *multi = true;
129 break;
130 }
131 }
132}
133
134/*
135 ********************************************************************************
136 ******************** RTFIO::FontTableEntry::FontTableEntry *********************
137 ********************************************************************************
138 */
139RTFIO::FontTableEntry::FontTableEntry ()
140 : fFontName ()
141 , fFNum (-1)
142 , fFamily (eNil)
143 , fCharSet (static_cast<uint8_t> (-1))
144 , fPitch (0)
145 , fCodePage (0)
146{
147}
148
149/*
150 ********************************************************************************
151 ********************************** RTFIO::FontTable ****************************
152 ********************************************************************************
153 */
154using FontTableEntry = RTFIO::FontTableEntry;
155using FontTable = RTFIO::FontTable;
156FontTable::FontTable ()
157 : fEntries ()
158{
159}
160
161FontTable::FontTable (const vector<FontTableEntry>& fontTable)
162 : fEntries{fontTable}
163{
164}
165
166IncrementalFontSpecification FontTable::GetFontSpec (int fontNumber)
167{
168 const FontTableEntry* ftep = LookupEntryByNumber (fontNumber);
169 if (ftep == nullptr) {
170 return IncrementalFontSpecification{}; // See spr#0696 0 some docs leave bad \font#s - so don't blow up - just no font spec!
171 }
172 IncrementalFontSpecification fontSpec;
173#if qStroika_Foundation_Common_Platform_Windows || qStroika_FeatureSupported_XWindows
174 fontSpec.SetFontNameSpecifier (ftep->fFontName.c_str ());
175#endif
176 return fontSpec;
177}
178
179const FontTableEntry* FontTable::LookupEntryByNumber (int fontNumber)
180{
181 for (size_t i = 0; i < fEntries.size (); ++i) {
182 const FontTableEntry& fte = fEntries[i];
183 if (fte.fFNum == fontNumber) {
184 return &fte;
185 }
186 }
187 return nullptr;
188}
189
190const FontTableEntry* FontTable::LookupEntryByName (const SDKString& name)
191{
192 for (size_t i = 0; i < fEntries.size (); ++i) {
193 const FontTableEntry& fte = fEntries[i];
194 if (fte.fFontName == name) {
195 return &fte;
196 }
197 }
198 return nullptr;
199}
200
201FontTableEntry FontTable::Add (const FontTableEntry& newEntry)
202{
203 int newFontNumber = FindSmallestUnusedFontNumber ();
204 FontTableEntry newerEntry = newEntry;
205 newerEntry.fFNum = newFontNumber;
206 fEntries.push_back (newerEntry);
207 return newerEntry;
208}
209
210int FontTable::FindSmallestUnusedFontNumber () const
211{
212 // Not terribly efficient, but OK for small numbers... LGP 960825
213 int tryThis = 0;
214Again:
215 for (size_t i = 0; i < fEntries.size (); ++i) {
216 const FontTableEntry& fte = fEntries[i];
217 if (fte.fFNum == tryThis) {
218 ++tryThis;
219 goto Again;
220 }
221 }
222 return tryThis;
223}
224
225/*
226 ********************************************************************************
227 ******************************** RTFIO::ColorTable *****************************
228 ********************************************************************************
229 */
230RTFIO::ColorTable::ColorTable ()
231 : fEntries ()
232{
233}
234
235RTFIO::ColorTable::ColorTable (const vector<Color>& colorTable)
236 : fEntries (colorTable)
237{
238}
239
240Color RTFIO::ColorTable::LookupColor (size_t colorNumber) const
241{
242 if (colorNumber < 0 or colorNumber >= fEntries.size ()) {
243 Execution::Throw (DataExchange::BadFormatException::kThe); // font number not found!
244 }
245 return fEntries[colorNumber];
246}
247
248size_t RTFIO::ColorTable::LookupColor (const Color& color) const
249{
250 for (size_t i = 0; i < fEntries.size (); ++i) {
251 const Color& c = fEntries[i];
252 if (c == color) {
253 return i;
254 }
255 }
256 Ensure (false);
257 return 0;
258}
259
260size_t RTFIO::ColorTable::EnterColor (const Color& color)
261{
262 for (size_t i = 0; i < fEntries.size (); ++i) {
263 const Color& c = fEntries[i];
264 if (c == color) {
265 return i;
266 }
267 }
268 fEntries.push_back (color);
269 return fEntries.size () - 1;
270}
271
272/*
273 ********************************************************************************
274 ****************************** RTFIO::ListTableEntry ***************************
275 ********************************************************************************
276 */
277
278RTFIO::ListTableEntry::ListTableEntry ()
279 : fListID (0)
280 , fListTemplateID (0)
281 , fListStyle (eListStyle_None)
282 , fFontID (0)
283{
284}
285
286/*
287 ********************************************************************************
288 ***************************** RTFIO::ListOverrideTableEntry ********************
289 ********************************************************************************
290 */
291RTFIO::ListOverrideTableEntry::ListOverrideTableEntry ()
292 : fListID (0)
293{
294}
295
296/*
297 ********************************************************************************
298 ******************************** RTFIO::ListTables *****************************
299 ********************************************************************************
300 */
301RTFIO::ListTables::ListTables ()
302 : fEntries ()
303 , fOverrideEntries ()
304{
305}
306
307RTFIO::ListTables::ListTables (const vector<ListTableEntry>& listTableEntries, const vector<ListOverrideTableEntry>& listOverrideTableEntries)
308 : fEntries (listTableEntries)
309 , fOverrideEntries (listOverrideTableEntries)
310{
311}
312
313/*
314 ********************************************************************************
315 ********************************** RTFInfo *************************************
316 ********************************************************************************
317 */
318#if qStroika_Frameworks_Led_SupportGDI
319const Led_PrivateEmbeddingTag RTFIO::kRTFBodyGroupFragmentEmbeddingTag = "RTFBFrag";
320#if qStroika_Foundation_Common_Platform_Windows
321const Led_ClipFormat RTFIO::kRTFBodyGroupFragmentClipFormat = static_cast<Led_ClipFormat> (::RegisterClipboardFormat (_T("RTFF")));
322#else
323const Led_ClipFormat RTFIO::kRTFBodyGroupFragmentClipFormat = static_cast<Led_ClipFormat> ('RTFF');
324#endif
325#endif
326
327inline RTFIO::ControlWordNameMap RTFIO::mkDefaultControlWordNameMap ()
328{
329 RTFIO::ControlWordNameMap table;
330#if qUseMapForControlWordMap
331#define TAB_INS_METHOD(name) \
332 Assert (table.size () == RTFIO::eControlAtom_##name); \
333 table.insert (ControlWordNameMap::value_type (#name, RTFIO::eControlAtom_##name))
334#else
335#define TAB_INS_METHOD(name) \
336 Assert (table.size () == RTFIO::eControlAtom_##name); \
337 table.push_back (ControlWordNameMap::value_type (#name, RTFIO::eControlAtom_##name))
338#endif
339
340 TAB_INS_METHOD (tab);
341 TAB_INS_METHOD (bullet);
342 TAB_INS_METHOD (endash);
343 TAB_INS_METHOD (emdash);
344 TAB_INS_METHOD (lquote);
345 TAB_INS_METHOD (rquote);
346 TAB_INS_METHOD (ldblquote);
347 TAB_INS_METHOD (rdblquote);
348
349 TAB_INS_METHOD (ansi);
350 TAB_INS_METHOD (author);
351 TAB_INS_METHOD (b);
352 TAB_INS_METHOD (blue);
353 TAB_INS_METHOD (brdrs);
354 TAB_INS_METHOD (brdrth);
355 TAB_INS_METHOD (brdrsh);
356 TAB_INS_METHOD (brdrdb);
357 TAB_INS_METHOD (brdrdot);
358 TAB_INS_METHOD (brdrdash);
359 TAB_INS_METHOD (brdrhair);
360 TAB_INS_METHOD (brdrdashsm);
361 TAB_INS_METHOD (brdrdashd);
362 TAB_INS_METHOD (brdrdashdd);
363 TAB_INS_METHOD (brdrtriple);
364 TAB_INS_METHOD (brdrtnthsg);
365 TAB_INS_METHOD (brdrthtnsg);
366 TAB_INS_METHOD (brdrtnthtnsg);
367 TAB_INS_METHOD (brdrtnthmg);
368 TAB_INS_METHOD (brdrthtnmg);
369 TAB_INS_METHOD (brdrtnthtnmg);
370 TAB_INS_METHOD (brdrtnthlg);
371 TAB_INS_METHOD (brdrthtnlg);
372 TAB_INS_METHOD (brdrtnthtnlg);
373 TAB_INS_METHOD (brdrwavy);
374 TAB_INS_METHOD (brdrwavydb);
375 TAB_INS_METHOD (brdrdashdotstr);
376 TAB_INS_METHOD (brdremboss);
377 TAB_INS_METHOD (brdrengrave);
378 TAB_INS_METHOD (brdrw);
379 TAB_INS_METHOD (brdrcf);
380 TAB_INS_METHOD (cchs);
381 TAB_INS_METHOD (cell);
382 TAB_INS_METHOD (cellx);
383 TAB_INS_METHOD (cf);
384 TAB_INS_METHOD (clcbpat);
385 TAB_INS_METHOD (cpg);
386 TAB_INS_METHOD (colortbl);
387 TAB_INS_METHOD (deff);
388 TAB_INS_METHOD (deflang);
389 TAB_INS_METHOD (deftab);
390 TAB_INS_METHOD (deleted);
391 TAB_INS_METHOD (dibitmap);
392 TAB_INS_METHOD (dn);
393 TAB_INS_METHOD (emfblip);
394 TAB_INS_METHOD (f);
395 TAB_INS_METHOD (fbidi);
396 TAB_INS_METHOD (fcharset);
397 TAB_INS_METHOD (fdecor);
398 TAB_INS_METHOD (fi);
399 TAB_INS_METHOD (fmodern);
400 TAB_INS_METHOD (footer);
401 TAB_INS_METHOD (fnil);
402 TAB_INS_METHOD (fonttbl);
403 TAB_INS_METHOD (fprq);
404 TAB_INS_METHOD (froman);
405 TAB_INS_METHOD (fs);
406 TAB_INS_METHOD (fswiss);
407 TAB_INS_METHOD (fscript);
408 TAB_INS_METHOD (ftech);
409 TAB_INS_METHOD (green);
410 TAB_INS_METHOD (header);
411 TAB_INS_METHOD (i);
412 TAB_INS_METHOD (ilvl);
413 TAB_INS_METHOD (info);
414 TAB_INS_METHOD (intbl);
415 TAB_INS_METHOD (jpegblip);
416 TAB_INS_METHOD (li);
417 TAB_INS_METHOD (line);
418 TAB_INS_METHOD (listtext);
419 TAB_INS_METHOD (ledprivateobjectembeddingformat);
420 TAB_INS_METHOD (ls);
421 TAB_INS_METHOD (mac);
422 TAB_INS_METHOD (macpict);
423 TAB_INS_METHOD (margb);
424 TAB_INS_METHOD (margl);
425 TAB_INS_METHOD (margr);
426 TAB_INS_METHOD (margt);
427 TAB_INS_METHOD (objdata);
428 TAB_INS_METHOD (object);
429 TAB_INS_METHOD (objemb);
430 TAB_INS_METHOD (objh);
431 TAB_INS_METHOD (objscalex);
432 TAB_INS_METHOD (objscaley);
433 TAB_INS_METHOD (objw);
434 TAB_INS_METHOD (outl);
435 TAB_INS_METHOD (paperh);
436 TAB_INS_METHOD (paperw);
437 TAB_INS_METHOD (par);
438 TAB_INS_METHOD (pard);
439 TAB_INS_METHOD (pc);
440 TAB_INS_METHOD (pca);
441 TAB_INS_METHOD (pich);
442 TAB_INS_METHOD (pichgoal);
443 TAB_INS_METHOD (picscalex);
444 TAB_INS_METHOD (picscaley);
445 TAB_INS_METHOD (pict);
446 TAB_INS_METHOD (picw);
447 TAB_INS_METHOD (picwgoal);
448 TAB_INS_METHOD (plain);
449 TAB_INS_METHOD (pmmetafile);
450 TAB_INS_METHOD (pn);
451 TAB_INS_METHOD (pngblip);
452 TAB_INS_METHOD (pntext);
453 TAB_INS_METHOD (qc);
454 TAB_INS_METHOD (qj);
455 TAB_INS_METHOD (ql);
456 TAB_INS_METHOD (qr);
457 TAB_INS_METHOD (red);
458 TAB_INS_METHOD (result);
459 TAB_INS_METHOD (ri);
460 TAB_INS_METHOD (row);
461 TAB_INS_METHOD (rtf);
462 TAB_INS_METHOD (sa);
463 TAB_INS_METHOD (sb);
464 TAB_INS_METHOD (shad);
465 TAB_INS_METHOD (sl);
466 TAB_INS_METHOD (slmult);
467 TAB_INS_METHOD (sub);
468 TAB_INS_METHOD (super);
469 TAB_INS_METHOD (strike);
470 TAB_INS_METHOD (stylesheet);
471 TAB_INS_METHOD (trleft);
472 TAB_INS_METHOD (trgaph);
473 TAB_INS_METHOD (trowd);
474 TAB_INS_METHOD (trpaddb);
475 TAB_INS_METHOD (trpaddl);
476 TAB_INS_METHOD (trpaddr);
477 TAB_INS_METHOD (trpaddt);
478 TAB_INS_METHOD (trspdb);
479 TAB_INS_METHOD (trspdl);
480 TAB_INS_METHOD (trspdr);
481 TAB_INS_METHOD (trspdt);
482 TAB_INS_METHOD (tx);
483 TAB_INS_METHOD (u);
484 TAB_INS_METHOD (uc);
485 TAB_INS_METHOD (ul);
486 TAB_INS_METHOD (ulnone);
487 TAB_INS_METHOD (up);
488 TAB_INS_METHOD (v);
489 TAB_INS_METHOD (wbitmap);
490 TAB_INS_METHOD (wmetafile);
491#undef TAB_INS_METHOD
492 Assert (table.size () == RTFIO::eControlAtomDynamicRangeStart);
493#if !qUseMapForControlWordMap
494 sort (table.begin (), table.end ());
495#endif
496 return table;
497}
498RTFIO::ControlWordNameMap RTFIO::sControlWordNameMap = RTFIO::mkDefaultControlWordNameMap ();
499
500string RTFIO::GetAtomName (ControlWordAtom atom)
501{
502 using ITER = RTFIO::ControlWordNameMap::const_iterator;
503 ITER start = sControlWordNameMap.begin ();
504 ITER end = sControlWordNameMap.end ();
505 for (ITER it = start; it != end; ++it) {
506 if (it->second == atom) {
507 return string{it->first};
508 }
509 }
510 Assert (false);
511 return "";
512}
513
514#if !qUseMapForControlWordMap
515struct RTFIO::StringNControlWordAtom_Comparator : binary_function<RTFIO::StringNControlWordAtom, const char*, bool> {
516 bool operator() (const RTFIO::StringNControlWordAtom& x, const char* y) const
517 {
518 return x.first < y;
519 }
520};
521#endif
522RTFIO::ControlWordAtom RTFIO::EnterControlWord (
523#if qUseMapForControlWordMap
524 const ControlWordAtomName& controlWord
525#else
526 const char* controlWord
527#endif
528)
529{
530#if !qUseMapForControlWordMap
531 RequireNotNull (controlWord);
532#endif
533
534 using ITER = ControlWordNameMap::iterator;
535#if qUseMapForControlWordMap
536 ITER i = sControlWordNameMap.find (controlWord);
537 if (i == sControlWordNameMap.end ()) {
538 ControlWordAtom newVal = ControlWordAtom (eControlAtomDynamicRangeStart + sControlWordNameMap.size ());
539 i = sControlWordNameMap.insert (ControlWordNameMap::value_type (controlWord, newVal)).first;
540 }
541 return i->second;
542#else
543 ITER start = sControlWordNameMap.begin ();
544 ITER end = sControlWordNameMap.end ();
545 ITER i = lower_bound (start, end, controlWord, StringNControlWordAtom_Comparator ());
546 if (i != end and controlWord == (*i).first) {
547 return (*i).second;
548 }
549 ControlWordAtom newVal = ControlWordAtom (eControlAtomDynamicRangeStart + sControlWordNameMap.size ());
550 sControlWordNameMap.insert (i, ControlWordNameMap::value_type (controlWord, newVal));
551 return newVal;
552#endif
553}
554
555#if qStroika_Frameworks_Led_SupportGDI
556/*
557 ********************************************************************************
558 ************************ RTFIO::RTFOLEEmbedding ********************************
559 ********************************************************************************
560 */
561const Led_PrivateEmbeddingTag RTFIO::RTFOLEEmbedding::kEmbeddingTag = "OLE2RTFEm";
562#endif
563
564/*
565 ********************************************************************************
566 **************** StyledTextIOReader_RTF::ReaderContext *************************
567 ********************************************************************************
568 */
569using ReaderContext = StyledTextIOReader_RTF::ReaderContext;
570
571/*
572 ********************************************************************************
573 ********** StyledTextIOReader_RTF::ReaderContext::SinkStreamDestination ********
574 ********************************************************************************
575 */
576/*
577 * SPR#0952,0968.
578 * EndParagraph/SetContext/AboutToChange () delayed handling code.
579 * The REASON behind all of this is that RTF will sometimes end a paragraph (with a \par) and our internal handling
580 * will set certain styles (say cuz an RTF scope closes - so values reset) - but we don't want this to force the
581 * emitting of a NEWLINE (cuz RTF docs will sometimes end in a \par when there should be no final NEWLINE in the doc).
582 *
583 * So - we need this complex machinery to delay handling of the \par ("\n") and final returning "SetContext" calls. which
584 * preserves their ordering, but allows them to sometimes be cut-off and not executed.
585 *
586 * Whenever an EndParagraph () happens - we force any delayed handling of SetContext() calls. However - if we have a pending
587 * EndParagraph (someone called EndParagraph but hasn't yet done anything else 'intersting to force it to be flushed), then
588 * we still allow a SetContext call to be 'cashed' and not yet handled.
589 *
590 * So - for example - the RTF "\par }" will cause TWO things to be delayed, whereas the RTF "}\par" will result in
591 * only the delayed para handling.
592 *
593 * Note - if we get "\par}}" its not entirely clear what todo. The safest thing is that two successive SetContext() calls with nothing else
594 * in between would cause a flushing - but then we MIGHT end up where stuff was nested by some crazy RTF writer an extra level - and
595 * we'd get the extra garbage char (newline) emitted. Not tragic - but not great. Or we could treat this as just ignoring the previous
596 * SetContext call. This should happen rarely - and I'm not sure what is best. For now - I think I'll just ignore the prev call and allow
597 * multiple SetContext () calls to happen and just ignore earlier ones - LGP 2001-08-23.
598 */
599using SinkStreamDestination = ReaderContext::SinkStreamDestination;
600StyledTextIOReader_RTF::ReaderContext::SinkStreamDestination::SinkStreamDestination (StyledTextIOReader_RTF& reader)
601 : Destination_ ()
602 , fSinkStream (reader.GetSinkStream ())
603 , fRTFInfo (reader.GetRTFInfo ())
604 , fReader (reader)
605{
606}
607
608SinkStreamDestination::~SinkStreamDestination ()
609{
610 // must FLUSH THIS GUY BEFORE DTOR - cannot flush here cuz that could raise exception, and
611 // (at least with MWERKS CW9) that appears to cause unexpected() to be called - illegal in C++
612 // to throw from DTOR? - LGP 960921
613 Require (fTCharsInSmallBuffer == 0);
614}
615
616void SinkStreamDestination::AppendText (const Led_tChar* text, size_t nTChars)
617{
618 AboutToChange ();
619 AppendText_ (text, nTChars);
620}
621
622#if qStroika_Frameworks_Led_SupportGDI
623void SinkStreamDestination::AppendEmbedding (SimpleEmbeddedObjectStyleMarker* embedding)
624{
625 // NB: we PROBABLY should apply the current font to this region as well, but so far - at least - it
626 // has no significance. So ignore for now ... LGP 961003
627
628 // Cannot easily see how to cache inserting these. They should be rare enuf not worth
629 // worrying about.
630 AboutToChange ();
631 Flush ();
632 fSinkStream.AppendEmbedding (embedding);
633}
634#endif
635
636void SinkStreamDestination::AppendSoftLineBreak ()
637{
638 AboutToChange ();
639 Flush ();
640 fSinkStream.AppendSoftLineBreak ();
641}
642
643void SinkStreamDestination::EndParagraph ()
644{
645 AboutToChange ();
646 fParaEndedFlag = true;
647}
648
649void SinkStreamDestination::UseFont (const IncrementalFontSpecification& fontSpec)
650{
651 AboutToChange ();
652 if (fCurrentContext.fFontSpec != fontSpec) {
653 Flush ();
654 fCurrentContext.fFontSpec = fontSpec;
655 }
656}
657
658void SinkStreamDestination::SetJustification (Justification justification)
659{
660 AboutToChange ();
661 if (fCurrentContext.fJustification != justification) {
662 Flush ();
663 fSinkStream.SetJustification (fCurrentContext.fJustification = justification);
664 }
665}
666
667void SinkStreamDestination::SetTabStops (const StandardTabStopList& tabStops)
668{
669 AboutToChange ();
670 if (fCurrentContext.fTabStops != tabStops) {
671 Flush ();
672 fSinkStream.SetStandardTabStopList (fCurrentContext.fTabStops = tabStops);
673 }
674}
675
676void SinkStreamDestination::SetFirstIndent (TWIPS tx)
677{
678 AboutToChange ();
679 if (fCurrentContext.fFirstIndent != tx) {
680 Flush ();
681 fSinkStream.SetFirstIndent (fCurrentContext.fFirstIndent = tx);
682 }
683}
684
685void SinkStreamDestination::SetLeftMargin (TWIPS lhs)
686{
687 AboutToChange ();
688 if (fCurrentContext.fLeftMargin != lhs) {
689 Flush ();
690 fSinkStream.SetLeftMargin (fCurrentContext.fLeftMargin = lhs);
691 }
692}
693
694void SinkStreamDestination::SetRightMargin (TWIPS rhs)
695{
696 AboutToChange ();
697 Flush ();
698
699 TWIPS effectivePaperSize = fRTFInfo.GetEffectiveDrawingWidth ();
700 TWIPS realMargin = TWIPS (effectivePaperSize - rhs);
701 if (realMargin < 0) {
702 realMargin = TWIPS (1);
703 }
704 fSinkStream.SetRightMargin (fCurrentContext.fRightMargin = realMargin);
705}
706
707void SinkStreamDestination::SetSpaceBefore (TWIPS tx)
708{
709 AboutToChange ();
710 if (fCurrentContext.fSpaceBefore != tx) {
711 Flush ();
712 fSinkStream.SetSpaceBefore (fCurrentContext.fSpaceBefore = tx);
713 }
714}
715
716void SinkStreamDestination::SetSpaceAfter (TWIPS tx)
717{
718 AboutToChange ();
719 if (fCurrentContext.fSpaceAfter != tx) {
720 Flush ();
721 fSinkStream.SetSpaceAfter (fCurrentContext.fSpaceAfter = tx);
722 }
723}
724
725void SinkStreamDestination::SetSpaceBetweenLines (CoordinateType sl)
726{
727 AboutToChange ();
728 if (fCurrentContext.fSpaceBetweenLines != sl) {
729 Flush ();
730 fSinkStream.SetLineSpacing (mkLineSpacing_From_RTFValues_ (fCurrentContext.fSpaceBetweenLines = sl, fCurrentContext.fSpaceBetweenLinesMult));
731 }
732}
733
734void SinkStreamDestination::SetSpaceBetweenLinesMult (bool multipleLineSpacing)
735{
736 AboutToChange ();
737 if (fCurrentContext.fSpaceBetweenLinesMult != multipleLineSpacing) {
738 Flush ();
739 fSinkStream.SetLineSpacing (mkLineSpacing_From_RTFValues_ (fCurrentContext.fSpaceBetweenLines,
740 fCurrentContext.fSpaceBetweenLinesMult = multipleLineSpacing));
741 }
742}
743
744void SinkStreamDestination::SetTextHidden (bool hidden)
745{
746 AboutToChange ();
747 if (fCurrentContext.fTextHidden != hidden) {
748 Flush ();
749 fSinkStream.SetTextHidden (fCurrentContext.fTextHidden = hidden);
750 }
751}
752
753void SinkStreamDestination::SetInTable (bool inTable)
754{
755 AboutToChange ();
756 if (fInTable != inTable) {
757 Flush ();
758 fInTable = inTable;
759 }
760}
761
762void SinkStreamDestination::EndRow (bool forceEmit)
763{
764 if (forceEmit and not fTableInRow) {
765 AssureTableOpen ();
766 DoStartRow ();
767 }
768 if (fTableInRow) {
769 Flush ();
770 if (fTableInCell) {
771 EndCell ();
772 }
773
774 fSinkStream.EndTableRow ();
775 fTableInRow = false;
776 }
777 Ensure (not fTableInRow);
778 Ensure (not fTableInCell);
779 SetInTable (false); // not SURE this is right - but I THINK \intbl needs to be repeated after each row???
780}
781
782void SinkStreamDestination::EndCell (bool forceEmit)
783{
784 if (forceEmit and not fTableInCell) {
785 AssureTableOpen ();
786 if (not fTableInRow) {
787 DoStartRow ();
788 }
789 DoStartCell ();
790 }
791 if (fTableInCell) {
792 Flush ();
793
794 size_t thisCellNum = fTableNextCellNum - 1;
795 if (thisCellNum < fThisRow.fCellInfosForThisRow.size ()) {
796 fSinkStream.SetCellBackColor (fThisRow.fCellInfosForThisRow[thisCellNum].f_clcbpat);
797 }
798 fSinkStream.EndTableCell ();
799 fTableInCell = false;
800 }
801}
802
803void SinkStreamDestination::SetListStyle (ListStyle listStyle)
804{
805 AboutToChange ();
806 if (fCurrentContext.fListStyle != listStyle) {
807 Flush ();
808 fSinkStream.SetListStyle (fCurrentContext.fListStyle = listStyle);
809 }
810}
811
812void SinkStreamDestination::SetListIndentLevel (unsigned char indentLevel)
813{
814 AboutToChange ();
815 if (fCurrentContext.fListIndentLevel != indentLevel) {
816 Flush ();
817 fSinkStream.SetListIndentLevel (fCurrentContext.fListIndentLevel = indentLevel);
818 }
819}
820
821void SinkStreamDestination::SetTableBorderColor (Color c)
822{
823 AboutToChange ();
824 Flush ();
825 fSinkStream.SetTableBorderColor (c);
826}
827
828void SinkStreamDestination::SetCellX (TWIPS cellx)
829{
830 AboutToChange ();
831 /*
832 * Ends a <celldef> according to the RTF 1.5 spec.
833 */
834 {
835 /*
836 * The cellx value specifies the ENDPOINT of the cell, and we prefer to keep track of the WIDTH of each cell.
837 */
838 TWIPS sub = TWIPS{0};
839 for (auto i = fThisRow.fCellInfosForThisRow.begin (); i != fThisRow.fCellInfosForThisRow.end (); ++i) {
840 sub += (*i).f_cellx;
841 }
842 fNextCellInfo.f_cellx = cellx - sub;
843 }
844
845 fThisRow.fCellInfosForThisRow.push_back (fNextCellInfo);
846
847 fNextCellInfo = CellInfo (); // clear out to default values
848}
849
850void SinkStreamDestination::SetCellBackColor (const Color& c)
851{
852 AboutToChange ();
853 fNextCellInfo.f_clcbpat = c;
854}
855
856void SinkStreamDestination::Call_trowd ()
857{
858 fNextCellInfo = CellInfo (); // clear out to default values
859 fThisRow = RowInfo (); // ditto
860}
861
862void SinkStreamDestination::Set_trleft (TWIPS t)
863{
864 fThisRow.f_trleft = t;
865}
866
867void SinkStreamDestination::SetDefaultCellMarginsForRow_top (TWIPS t)
868{
869 fThisRow.fDefaultCellMargins.top = t;
870}
871
872void SinkStreamDestination::SetDefaultCellMarginsForRow_left (TWIPS t)
873{
874 fThisRow.fDefaultCellMargins.left = t;
875}
876
877void SinkStreamDestination::SetDefaultCellMarginsForRow_bottom (TWIPS t)
878{
879 fThisRow.fDefaultCellMargins.bottom = t;
880}
881
882void SinkStreamDestination::SetDefaultCellMarginsForRow_right (TWIPS t)
883{
884 fThisRow.fDefaultCellMargins.right = t;
885}
886
887void SinkStreamDestination::SetDefaultCellSpacingForRow_top (TWIPS t)
888{
889 // Because value written is only half real spacing - See RTF 1.7 spec
890 fThisRow.fDefaultCellSpacing.top = TWIPS (t * 2);
891}
892
893void SinkStreamDestination::SetDefaultCellSpacingForRow_left (TWIPS t)
894{
895 // Because value written is only half real spacing - See RTF 1.7 spec
896 fThisRow.fDefaultCellSpacing.left = TWIPS (t * 2);
897}
898
899void SinkStreamDestination::SetDefaultCellSpacingForRow_bottom (TWIPS t)
900{
901 // Because value written is only half real spacing - See RTF 1.7 spec
902 fThisRow.fDefaultCellSpacing.bottom = TWIPS (t * 2);
903}
904
905void SinkStreamDestination::SetDefaultCellSpacingForRow_right (TWIPS t)
906{
907 // Because value written is only half real spacing - See RTF 1.7 spec
908 fThisRow.fDefaultCellSpacing.right = TWIPS (t * 2);
909}
910
911void SinkStreamDestination::AssureTableOpen ()
912{
913 if (not fTableInRow and fTableNextRowNum == 0) {
914 fSinkStream.StartTable ();
915 fTableOpen = true;
916 }
917}
918
919void SinkStreamDestination::DoStartRow ()
920{
921 fSinkStream.StartTableRow ();
922 {
923 vector<TWIPS> cellWidths;
924 for (auto i = fThisRow.fCellInfosForThisRow.begin (); i != fThisRow.fCellInfosForThisRow.end (); ++i) {
925 const TWIPS kMinWidth = TWIPS{0};
926 TWIPS thisCellW = (*i).f_cellx;
927 if (i == fThisRow.fCellInfosForThisRow.begin ()) {
928 thisCellW -= fThisRow.f_trleft;
929 }
930
931 // This value of 3/2 * LHS is somewhat empirically derived from the output of MS Word XP. Its really quite
932 // hadly documented - the relationship between cell spacing and cellx values.
933 // LGP 2003-05-01 - SPR#1396 (now corresponding change in writer)
934 thisCellW -= TWIPS (3 * fThisRow.fDefaultCellSpacing.left / 2);
935
936 if (thisCellW < kMinWidth) {
937 thisCellW = kMinWidth;
938 }
939 cellWidths.push_back (thisCellW);
940 }
941 fSinkStream.SetCellWidths (cellWidths);
942 fSinkStream.SetDefaultCellMarginsForCurrentRow (fThisRow.fDefaultCellMargins.top, fThisRow.fDefaultCellMargins.left,
943 fThisRow.fDefaultCellMargins.bottom, fThisRow.fDefaultCellMargins.right);
944 fSinkStream.SetDefaultCellSpacingForCurrentRow (fThisRow.fDefaultCellSpacing.top, fThisRow.fDefaultCellSpacing.left,
945 fThisRow.fDefaultCellSpacing.bottom, fThisRow.fDefaultCellSpacing.right);
946 }
947 fTableInRow = true;
948 fTableNextCellNum = 0;
949 ++fTableNextRowNum;
950}
951
952void SinkStreamDestination::DoStartCell ()
953{
954 fSinkStream.StartTableCell (fThisRow.fCellInfosForThisRow[fTableNextCellNum].fColSpan);
955 fTableInCell = true;
956 ++fTableNextCellNum;
957}
958
959void SinkStreamDestination::DoEndTable ()
960{
961 if (fTableInRow) {
962 EndRow ();
963 }
964 if (fTableOpen) {
965 fSinkStream.EndTable ();
966 fTableOpen = false;
967 fTableNextCellNum = 0;
968 fTableNextRowNum = 0;
969 }
970 Ensure (not fTableInRow);
971 Ensure (not fTableOpen);
972}
973
974void SinkStreamDestination::Flush ()
975{
976 FlushParaEndings ();
977 if (fTCharsInSmallBuffer != 0) {
978 FontSpecification fsp = fSinkStream.GetDefaultFontSpec ();
979 fsp.MergeIn (fCurrentContext.fFontSpec);
980 fSinkStream.AppendText (fSmallBuffer, fTCharsInSmallBuffer, &fsp);
981 fTCharsInSmallBuffer = 0;
982 }
983}
984
985void SinkStreamDestination::Done ()
986{
987 fParaEndedFlag = false; // EXPLAIN -= SPR#0952
988 DoEndTable ();
989 fSinkStream.EndOfBuffer ();
990 Flush ();
991}
992
993SinkStreamDestination::Context SinkStreamDestination::GetContext () const
994{
995 return fCurrentContext;
996}
997
998void SinkStreamDestination::SetContext (const Context& c)
999{
1000 fParaEndBeforeNewContext = fParaEndedFlag;
1001 fNewContext = c;
1002 fNewContextPending = true;
1003}
1004
1005void SinkStreamDestination::ApplyContext (const Context& c)
1006{
1007 /*
1008 * See elaborate comment about SPR#0952,0968 above.
1009 */
1010 UseFont (c.fFontSpec);
1011 SetTabStops (c.fTabStops);
1012 SetJustification (c.fJustification);
1013 SetSpaceBefore (c.fSpaceBefore);
1014 SetSpaceAfter (c.fSpaceAfter);
1015 SetSpaceBetweenLines (c.fSpaceBetweenLines);
1016 SetSpaceBetweenLinesMult (c.fSpaceBetweenLinesMult);
1017 SetListStyle (c.fListStyle);
1018 SetListIndentLevel (c.fListIndentLevel);
1019 SetFirstIndent (c.fFirstIndent);
1020 SetLeftMargin (c.fLeftMargin);
1021 SetRightMargin (c.fRightMargin);
1022 SetTextHidden (c.fTextHidden);
1023}
1024
1025size_t SinkStreamDestination::current_offset () const
1026{
1027 AboutToChange ();
1028 return fSinkStream.current_offset () + fTCharsInSmallBuffer;
1029}
1030
1031void SinkStreamDestination::InsertMarker (Marker* m, size_t at, size_t length, MarkerOwner* markerOwner)
1032{
1033 Require (at <= current_offset ());
1034 AboutToChange ();
1035 // Flush () before adding markers if any part of the marker must wrap text inserted text. Must let
1036 // the textstore know about the extra text before we can safely add the marker.
1037 if (at + length >= fSinkStream.current_offset ()) {
1038 Flush ();
1039 }
1040 fSinkStream.InsertMarker (m, at, length, markerOwner);
1041}
1042
1043void SinkStreamDestination::AppendText_ (const Led_tChar* text, size_t nTChars)
1044{
1045 if (nTChars > 0) {
1046 if (fInTable) {
1047#if 1
1048 AssureTableOpen ();
1049#else
1050 if (not fTableInRow and fTableNextRowNum == 0) {
1051 fSinkStream.StartTable ();
1052 fTableOpen = true;
1053 }
1054#endif
1055 if (not fTableInRow) {
1056 DoStartRow ();
1057 }
1058 if (not fTableInCell) {
1059 DoStartCell ();
1060 }
1061 }
1062 else {
1063 DoEndTable ();
1064 }
1065 }
1066
1067 if (fTCharsInSmallBuffer + nTChars < (Memory::NEltsOf (fSmallBuffer))) {
1068 (void)::memcpy (&fSmallBuffer[fTCharsInSmallBuffer], text, nTChars * sizeof (Led_tChar));
1069 fTCharsInSmallBuffer += nTChars;
1070 }
1071 else {
1072 Flush ();
1073 Assert (fTCharsInSmallBuffer == 0);
1074
1075 if (nTChars < (Memory::NEltsOf (fSmallBuffer))) {
1076 (void)::memcpy (&fSmallBuffer[0], text, nTChars * sizeof (Led_tChar));
1077 fTCharsInSmallBuffer = nTChars;
1078 }
1079 else {
1080 // doesn't fit in our buffer, so write it directly...
1081 FontSpecification fsp = fSinkStream.GetDefaultFontSpec ();
1082 fsp.MergeIn (fCurrentContext.fFontSpec);
1083 fSinkStream.AppendText (text, nTChars, &fsp);
1084 }
1085 }
1086}
1087
1088void SinkStreamDestination::AboutToChange () const
1089{
1090 /*
1091 * See elaborate comment about SPR#0952,0968 above.
1092 */
1093 FlushParaEndings ();
1094 FlushSetContextCalls ();
1095}
1096
1097void SinkStreamDestination::FlushSetContextCalls () const
1098{
1099 /*
1100 * See elaborate comment about SPR#0952,0968 above.
1101 */
1102 if (fNewContextPending) {
1103 fNewContextPending = false;
1104 const_cast<SinkStreamDestination*> (this)->ApplyContext (fNewContext);
1105 }
1106}
1107
1108void SinkStreamDestination::FlushParaEndings () const
1109{
1110 /*
1111 * See elaborate comment about SPR#0952,0968 above.
1112 */
1113 if (fParaEndedFlag) {
1114 fParaEndedFlag = false;
1115 Led_tChar c = '\n';
1116 const_cast<SinkStreamDestination*> (this)->AppendText_ (&c, 1);
1117 }
1118}
1119
1120/*
1121 ********************************************************************************
1122 **** StyledTextIOReader_RTF::ReaderContext::SinkStreamDestination::CellInfo ****
1123 ********************************************************************************
1124 */
1125SinkStreamDestination::CellInfo::CellInfo ()
1126 : f_cellx{TWIPS{0}}
1127 , f_clcbpat{Color::kWhite}
1128 , fColSpan{1}
1129{
1130}
1131
1132/*
1133 ********************************************************************************
1134 **** StyledTextIOReader_RTF::ReaderContext::SinkStreamDestination::RowInfo ****
1135 ********************************************************************************
1136 */
1137SinkStreamDestination::RowInfo::RowInfo ()
1138 : f_trrh{TWIPS{0}}
1139 , f_trleft{TWIPS{0}}
1140 , fDefaultCellMargins{TWIPS{0}, TWIPS{0}, TWIPS{0}, TWIPS{0}}
1141 , fDefaultCellSpacing{TWIPS{0}, TWIPS{0}, TWIPS{0}, TWIPS{0}}
1142 , fCellInfosForThisRow{}
1143{
1144}
1145
1146/*
1147 ********************************************************************************
1148 ******************** StyledTextIOReader_RTF::ReaderContext *********************
1149 ********************************************************************************
1150 */
1151StyledTextIOReader_RTF::ReaderContext::ReaderContext (StyledTextIOReader_RTF& reader)
1152 : fReader{reader}
1153 , fDocumentCharacterSet (Characters::WellKnownCodePages::kANSI)
1154 , // ANSI default, according to RTF spec
1155 fCurrentInputCharSetEncoding_ (Characters::WellKnownCodePages::kANSI)
1156 , fMultiByteInputCharBuf ()
1157{
1158 memset (fMultiByteInputCharBuf, 0, sizeof (fMultiByteInputCharBuf));
1159#if qCannotAssignRValueAutoPtrToExistingOneInOneStepBug || qTroubleOverloadingXofXRefCTORWithTemplatedMemberCTOR
1160 unique_ptr<Destination_> x = unique_ptr<Destination_> (new SinkStreamDestination (reader));
1161 fDefaultDestination = x;
1162#else
1163 fDefaultDestination = unique_ptr<Destination_> (new SinkStreamDestination (reader));
1164#endif
1165 SetDestination (fDefaultDestination.get ());
1166}
1167
1168StyledTextIOReader_RTF::ReaderContext::~ReaderContext ()
1169{
1170 Require (fCurrentGroup == nullptr); // all our current groups must be deleted before this whole
1171 // reader context (else they would have pointers back to us
1172 // after we've been destroyed)
1173 delete fFontTable;
1174 delete fColorTable;
1175}
1176
1177void StyledTextIOReader_RTF::ReaderContext::UseInputCharSetEncoding (CodePage codePage)
1178{
1179 fCurrentInputCharSetEncoding_ = codePage;
1180}
1181
1182void StyledTextIOReader_RTF::ReaderContext::PutRawCharToDestination (char c)
1183{
1184 RequireNotNull (GetCurrentGroupContext ());
1185
1186 if (fSkipNextNChars_UC > 0) {
1187 fSkipNextNChars_UC--;
1188 return;
1189 }
1190
1191 CodePage codePage = GetCurrentInputCharSetEncoding ();
1192
1193 // Weird rules for handling cchs - from MS RTF 1.3 spec:
1194 //\cchsN Indicates any characters not belonging to the default
1195 // document character set and tells which character set they do belong to.
1196 // Macintosh character sets are represented by values greater than 255.
1197 // The values for N correspond to the values for the \fcharset control word.
1198 //
1199 // But this seems to neglect the concept of \cpg, or \fN&\fcharsets which could have set
1200 // the current character set.
1201 //
1202 // So my best guess to interpret this is that it only applies when the current characterset
1203 // IS the same as the document character set (AS OF LED 2.3).
1204 //
1205 // As of Led 3.0d6 (2000/04/29) - I've changed this slightly - so if a cchs is specified - it simply overrides
1206 // the code current codepage specification. My reason for this change was mostly cuz of what I saw in exising practice in
1207 // the few files I had that used \cchs, and because new new CodePage support didn't make it easy to tell if a character
1208 // existed in an existing code page (and I think my old tabular mechanism was highly questionable, and incomplete).
1209 //
1210
1211 if ((GetCurrentGroupContext ()->fCCHSCodePage != 0) and fDocumentCharacterSet == codePage) {
1212 codePage = GetCurrentGroupContext ()->fCCHSCodePage;
1213 }
1214
1215 if (fMultiByteInputCharBuf[0] == '\0') {
1216 fMultiByteInputCharBuf[0] = c;
1217 fMultiByteInputCharBuf[1] = '\0';
1218 }
1219 else {
1220 /*
1221 * If we have a PENDING first-byte - then append this to our buffer, and pretend
1222 * the we read these two bytes at once.
1223 */
1224 fMultiByteInputCharBuf[1] = c; // set it even if its bad so OnBadUserInput can peek()
1225 }
1226
1227 wchar_t outChar;
1228 size_t nOutChars = 1;
1229
1230 auto inBuf = span{reinterpret_cast<const byte*> (fMultiByteInputCharBuf), fMultiByteInputCharBuf[1] == '\0' ? 1u : 2u};
1231 nOutChars = Characters::CodeCvt<wchar_t>{codePage}.Bytes2Characters (&inBuf, span{&outChar, 1}).size ();
1232 Assert (nOutChars == 0 or nOutChars == 1);
1233 if (nOutChars == 1) {
1234 GetDestination ().AppendText (&outChar, 1);
1235 fMultiByteInputCharBuf[0] = '\0';
1236 }
1237 else {
1238 if (fMultiByteInputCharBuf[1] == '\0') {
1239 // Assume was the first byte of a multi-byte sequenece
1240 // and we should use it next time around...
1241 }
1242 else {
1243 // just assume garbage character - and use our default 'garbage char'
1244 Led_tChar u = GetReader ().GetDefaultUnsupportedCharacterChar ();
1245 GetDestination ().AppendText (&u, 1);
1246 fMultiByteInputCharBuf[0] = '\0';
1247 }
1248 }
1249}
1250
1251/*
1252 ********************************************************************************
1253 *********** StyledTextIOReader_RTF:ReaderContext::GroupContext *****************
1254 ********************************************************************************
1255 */
1256StyledTextIOReader_RTF::ReaderContext::GroupContext::GroupContext (ReaderContext& readerContext)
1257 : fReaderContext (readerContext)
1258 , fParentGroup (readerContext.fCurrentGroup)
1259 , fCurrentGroupStartIdx (readerContext.GetReader ().GetSrcStream ().current_offset ())
1260 , fCurrentCodePage (WellKnownCodePages::kANSI)
1261 , fCCHSCodePage (0)
1262 , fDestinationContext () /// LGP 2001-08-22- ------ FOR NOW - BOGUS INITIAL VALUE- BUT SHOULD GET FROM readerContext::Destination::GetContext() rather than from parent group!!!
1263{
1264 if (fParentGroup != nullptr) {
1265 fDestinationContext = fParentGroup->fDestinationContext; // SEE ABOVE - DON'T GET FROM PARENT GROUP BUT FROM CUR DESTINATION CONTEXT!!!
1266 fCurrentCodePage = fParentGroup->fCurrentCodePage;
1267 fCCHSCodePage = fParentGroup->fCCHSCodePage;
1268 }
1269 readerContext.fCurrentGroup = this;
1270}
1271
1272StyledTextIOReader_RTF::ReaderContext::GroupContext::~GroupContext ()
1273{
1274 Assert (fReaderContext.fCurrentGroup == this); // cuz these always (even with exceptions) must be unwound in
1275 // reverse order of creation.
1276 fReaderContext.fCurrentGroup = fParentGroup;
1277 if (fReaderContext.fCurrentGroup != nullptr) {
1278 fReaderContext.UseInputCharSetEncoding (fReaderContext.fCurrentGroup->fCurrentCodePage);
1279 fReaderContext.GetDestination ().SetContext (fReaderContext.fCurrentGroup->fDestinationContext);
1280 }
1281}
1282
1283RTFIO::ControlWordAtomName::ControlWordAtomName (const char* c)
1284//:fName ()
1285{
1286 Characters::CString::Copy (fName, eMaxControlAtomNameLen, c);
1287}
1288
1289/*
1290 ********************************************************************************
1291 ******************************** StyledTextIOReader_RTF ************************
1292 ********************************************************************************
1293 */
1294const StyledTextIOReader_RTF::SpecialCharMappings StyledTextIOReader_RTF::kMappings[8] = {
1295 {RTFIO::eControlAtom_tab, 0x0009}, {RTFIO::eControlAtom_bullet, 0x2022}, {RTFIO::eControlAtom_endash, 0x2014},
1296 {RTFIO::eControlAtom_emdash, 0x2013}, {RTFIO::eControlAtom_lquote, 0x2018}, {RTFIO::eControlAtom_rquote, 0x2019},
1297 {RTFIO::eControlAtom_ldblquote, 0x201c}, {RTFIO::eControlAtom_rdblquote, 0x201d},
1298};
1299
1300StyledTextIOReader_RTF::StyledTextIOReader_RTF (SrcStream* srcStream, SinkStream* sinkStream, RTFInfo* rtfInfo)
1301 : StyledTextIOReader (srcStream, sinkStream)
1302 , fPlainFont ()
1303 , fDefaultUnsupportedCharacterChar (LED_TCHAR_OF ('?'))
1304 , fRTFInfo (rtfInfo == nullptr ? new RTFInfo () : rtfInfo)
1305 , fOwnRTFInfo (rtfInfo == nullptr)
1306#if qStroika_Foundation_Common_Platform_Windows
1307 , fCachedFontSize (0)
1308 , fCachedFontSizeTMHeight (0)
1309#endif
1310{
1311 /*
1312 * Set default value for 'plain font' - what is used when we encounter \plain
1313 * wild freakin guess??? - Seems to make some files read better? Docs really unclear about this...
1314 * After looking at some sample code, it looks like we SHOULD reset this to some predefined default.
1315 * Unclear what that default should be???
1316 */
1317 fPlainFont.SetPointSize (12);
1318#if qStroika_Foundation_Common_Platform_Windows
1319 fCachedFontSize = 12;
1320 fCachedFontSizeTMHeight = fPlainFont.PeekAtTMHeight ();
1321#endif
1322}
1323
1324StyledTextIOReader_RTF::~StyledTextIOReader_RTF ()
1325{
1326 if (fOwnRTFInfo) {
1327 delete fRTFInfo;
1328 }
1329}
1330
1331void StyledTextIOReader_RTF::Read ()
1332{
1333 ReaderContext readerContext (*this);
1334 try {
1335 ReadGroup (readerContext);
1336 }
1337 catch (ReadEOFException& /*eof*/) {
1338 // Signifies un unimportant error - cruft past the end of file. Don't treat this as an
1339 // error - at least for now. Just eat it (I wonder if this is a mistake? - LGP 960827)
1340 // If it IS a mistake, we should replace this IGNORE with a 'Led_ThrowBadFormatDataException ();'
1341 }
1342 //readerContext.fDefaultDestination->Flush ();
1343 readerContext.fDefaultDestination->Done ();
1344}
1345
1346bool StyledTextIOReader_RTF::QuickLookAppearsToBeRightFormat ()
1347{
1348 SrcStreamSeekSaver savePos (GetSrcStream ());
1349
1350 const char kStandardRTFOpenString[] = "{\\rtf";
1351 char buf[sizeof (kStandardRTFOpenString) - 1];
1352 size_t bytesRead = GetSrcStream ().read (buf, sizeof (buf));
1353 return ((bytesRead == sizeof (kStandardRTFOpenString) - 1) and ::memcmp (kStandardRTFOpenString, buf, sizeof (kStandardRTFOpenString) - 1) == 0);
1354}
1355
1356void StyledTextIOReader_RTF::ReadGroup (ReaderContext& readerContext)
1357{
1358 ReaderContext::GroupContext thisGroupContext (readerContext);
1359
1360 if (GetNextChar () != RTFIO::kRTFOpenGroupChar) {
1361 HandleBadlyFormattedInput (true);
1362 }
1363
1364 while (true) {
1365 char c = GetNextChar ();
1366 switch (c) {
1367 case RTFIO::kRTFOpenGroupChar: {
1368 // put open character back, and recursively read a group
1369 PutBackLastChar ();
1370 ReadGroup (readerContext);
1371 } break;
1372
1373 case RTFIO::kRTFCloseGroupChar: {
1374 return; // end of group
1375 } break;
1376
1377 case RTFIO::kRTFStartTagChar: {
1378 // put tag-start character back, and re-read the whole tag
1379 char nextChar = PeekNextChar ();
1380
1381 switch (nextChar) {
1382 // Though the RTF specs (1.4 - 1.7) clearly indicate that
1383 // only lower case letters can begin a control word, spec version 1.7
1384 // notes a dozen or so exceptions to this rule. WE should never WRITE any such
1385 // control words, but for backward compatabilities sake - we must still
1386 // be able to read them.
1387 // SPR#
1388 case 'A':
1389 case 'B':
1390 case 'C':
1391 case 'D':
1392 case 'E':
1393 case 'F':
1394 case 'G':
1395 case 'H':
1396 case 'I':
1397 case 'J':
1398 case 'K':
1399 case 'L':
1400 case 'M':
1401 case 'N':
1402 case 'O':
1403 case 'P':
1404 case 'Q':
1405 case 'R':
1406 case 'S':
1407 case 'T':
1408 case 'U':
1409 case 'V':
1410 case 'W':
1411 case 'X':
1412 case 'Y':
1413 case 'Z':
1414 case 'a':
1415 case 'b':
1416 case 'c':
1417 case 'd':
1418 case 'e':
1419 case 'f':
1420 case 'g':
1421 case 'h':
1422 case 'i':
1423 case 'j':
1424 case 'k':
1425 case 'l':
1426 case 'm':
1427 case 'n':
1428 case 'o':
1429 case 'p':
1430 case 'q':
1431 case 'r':
1432 case 's':
1433 case 't':
1434 case 'u':
1435 case 'v':
1436 case 'w':
1437 case 'x':
1438 case 'y':
1439 case 'z': {
1440 PutBackLastChar ();
1441 RTFIO::ControlWord cw = ReadControlWord ();
1442 if (HandleControlWord (readerContext, cw)) {
1443 return; // end of group
1444 }
1445 } break;
1446
1447 case '|': {
1448 ConsumeNextChar ();
1449 const wchar_t kFormula = 0x0006;
1450 Led_tChar cc = kFormula;
1451 CheckIfAboutToStartBody (readerContext);
1452 readerContext.GetDestination ().AppendText (&cc, 1);
1453 } break;
1454
1455 case '~': {
1456 ConsumeNextChar ();
1457 Led_tChar cc = kNonBreakingSpace;
1458 CheckIfAboutToStartBody (readerContext);
1459 readerContext.GetDestination ().AppendText (&cc, 1);
1460 } break;
1461
1462 case '-': {
1463 ConsumeNextChar ();
1464
1465 const wchar_t kOptionalHyphen = 0x00AD; // RTF 1.5 spec says "Optional Hyphen". This is the character from the UNICODE spec labeled "Soft Hyphen"
1466 // that was the closest match I could find
1467 Led_tChar cc = kOptionalHyphen;
1468 CheckIfAboutToStartBody (readerContext);
1469 readerContext.GetDestination ().AppendText (&cc, 1);
1470 } break;
1471
1472 case '_': {
1473 ConsumeNextChar ();
1474 const wchar_t kNonBreakingHyphen = 0x2011;
1475 Led_tChar cc = kNonBreakingHyphen;
1476 CheckIfAboutToStartBody (readerContext);
1477 readerContext.GetDestination ().AppendText (&cc, 1);
1478 } break;
1479
1480 case ':': {
1481 //???? subentry in an index???- not sure what todo but ignore??? No char to insert?
1482 } break;
1483
1484 case '*': {
1485 ReadCommentGroup (readerContext); // consumes end of group
1486 return; // end of group
1487 } break;
1488
1489 case '\'': {
1490 ConsumeNextChar ();
1491
1492 int number = ConvertReadSingleHexDigit_ (GetNextChar ());
1493 number *= 16;
1494 number += ConvertReadSingleHexDigit_ (GetNextChar ());
1495 c = static_cast<char> (number);
1496 goto ReadNormalChar;
1497 } break;
1498
1499 default: {
1500 // Then this is a quoted quote-char, and we should consume the first, and read
1501 // as a normal character the second
1502 ConsumeNextChar ();
1503 c = nextChar;
1504 goto ReadNormalChar;
1505 } break;
1506 }
1507 }; break;
1508
1509 case '\n':
1510 case '\r': {
1511 // ignored??
1512 } break;
1513
1514 default: {
1515 ReadNormalChar:
1516 CheckIfAboutToStartBody (readerContext);
1517 // Other characters simply get inserted into the current RTF destination
1518 readerContext.PutRawCharToDestination (c);
1519 } break;
1520 }
1521 }
1522}
1523
1524bool StyledTextIOReader_RTF::HandleControlWord (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
1525{
1526 switch (controlWord.fWord) {
1527 case RTFIO::eControlAtom_ansi:
1528 return (HandleControlWord_ansi (readerContext, controlWord));
1529 case RTFIO::eControlAtom_author:
1530 return (HandleControlWord_author (readerContext, controlWord));
1531 case RTFIO::eControlAtom_b:
1532 return (HandleControlWord_b (readerContext, controlWord));
1533 case RTFIO::eControlAtom_brdrs:
1534 case RTFIO::eControlAtom_brdrth:
1535 case RTFIO::eControlAtom_brdrsh:
1536 case RTFIO::eControlAtom_brdrdb:
1537 case RTFIO::eControlAtom_brdrdot:
1538 case RTFIO::eControlAtom_brdrdash:
1539 case RTFIO::eControlAtom_brdrhair:
1540 case RTFIO::eControlAtom_brdrdashsm:
1541 case RTFIO::eControlAtom_brdrdashd:
1542 case RTFIO::eControlAtom_brdrdashdd:
1543 case RTFIO::eControlAtom_brdrtriple:
1544 case RTFIO::eControlAtom_brdrtnthsg:
1545 case RTFIO::eControlAtom_brdrthtnsg:
1546 case RTFIO::eControlAtom_brdrtnthtnsg:
1547 case RTFIO::eControlAtom_brdrtnthmg:
1548 case RTFIO::eControlAtom_brdrthtnmg:
1549 case RTFIO::eControlAtom_brdrtnthtnmg:
1550 case RTFIO::eControlAtom_brdrtnthlg:
1551 case RTFIO::eControlAtom_brdrthtnlg:
1552 case RTFIO::eControlAtom_brdrtnthtnlg:
1553 case RTFIO::eControlAtom_brdrwavy:
1554 case RTFIO::eControlAtom_brdrwavydb:
1555 case RTFIO::eControlAtom_brdrdashdotstr:
1556 case RTFIO::eControlAtom_brdremboss:
1557 case RTFIO::eControlAtom_brdrengrave:
1558 case RTFIO::eControlAtom_brdrw:
1559 case RTFIO::eControlAtom_brdrcf:
1560 return (HandleControlWord_brdrXXX (readerContext, controlWord));
1561 case RTFIO::eControlAtom_cchs:
1562 return (HandleControlWord_cchs (readerContext, controlWord));
1563 case RTFIO::eControlAtom_cell:
1564 return (HandleControlWord_cell (readerContext, controlWord));
1565 case RTFIO::eControlAtom_cellx:
1566 return (HandleControlWord_cellx (readerContext, controlWord));
1567 case RTFIO::eControlAtom_cf:
1568 return (HandleControlWord_cf (readerContext, controlWord));
1569 case RTFIO::eControlAtom_clcbpat:
1570 return (HandleControlWord_clcbpat (readerContext, controlWord));
1571 case RTFIO::eControlAtom_cpg:
1572 return (HandleControlWord_cpg (readerContext, controlWord));
1573 case RTFIO::eControlAtom_colortbl:
1574 return (HandleControlWord_colortbl (readerContext, controlWord));
1575 case RTFIO::eControlAtom_deff:
1576 return (HandleControlWord_deff (readerContext, controlWord));
1577 case RTFIO::eControlAtom_deftab:
1578 return (HandleControlWord_deftab (readerContext, controlWord));
1579 case RTFIO::eControlAtom_deleted:
1580 return (HandleControlWord_deleted (readerContext, controlWord));
1581 case RTFIO::eControlAtom_dn:
1582 return (HandleControlWord_dn (readerContext, controlWord));
1583 case RTFIO::eControlAtom_f:
1584 return (HandleControlWord_f (readerContext, controlWord));
1585 case RTFIO::eControlAtom_fi:
1586 return (HandleControlWord_fi (readerContext, controlWord));
1587 case RTFIO::eControlAtom_footer:
1588 return (HandleControlWord_footer (readerContext, controlWord));
1589 case RTFIO::eControlAtom_fonttbl:
1590 return (HandleControlWord_fonttbl (readerContext, controlWord));
1591 case RTFIO::eControlAtom_fs:
1592 return (HandleControlWord_fs (readerContext, controlWord));
1593 case RTFIO::eControlAtom_header:
1594 return (HandleControlWord_header (readerContext, controlWord));
1595 case RTFIO::eControlAtom_i:
1596 return (HandleControlWord_i (readerContext, controlWord));
1597 case RTFIO::eControlAtom_ilvl:
1598 return (HandleControlWord_ilvl (readerContext, controlWord));
1599 case RTFIO::eControlAtom_info:
1600 return (HandleControlWord_info (readerContext, controlWord));
1601 case RTFIO::eControlAtom_intbl:
1602 return (HandleControlWord_intbl (readerContext, controlWord));
1603 case RTFIO::eControlAtom_li:
1604 return (HandleControlWord_li (readerContext, controlWord));
1605 case RTFIO::eControlAtom_line:
1606 return (HandleControlWord_line (readerContext, controlWord));
1607 case RTFIO::eControlAtom_listtext:
1608 return (HandleControlWord_listtext (readerContext, controlWord));
1609 case RTFIO::eControlAtom_ls:
1610 return (HandleControlWord_ls (readerContext, controlWord));
1611 case RTFIO::eControlAtom_mac:
1612 return (HandleControlWord_mac (readerContext, controlWord));
1613 case RTFIO::eControlAtom_margb:
1614 case RTFIO::eControlAtom_margl:
1615 case RTFIO::eControlAtom_margr:
1616 case RTFIO::eControlAtom_margt:
1617 return (HandleControlWord_margX (readerContext, controlWord));
1618 case RTFIO::eControlAtom_object:
1619 return (HandleControlWord_object (readerContext, controlWord));
1620 case RTFIO::eControlAtom_outl:
1621 return (HandleControlWord_outl (readerContext, controlWord));
1622 case RTFIO::eControlAtom_paperh:
1623 return (HandleControlWord_paperX (readerContext, controlWord));
1624 case RTFIO::eControlAtom_paperw:
1625 return (HandleControlWord_paperX (readerContext, controlWord));
1626 case RTFIO::eControlAtom_par:
1627 return (HandleControlWord_par (readerContext, controlWord));
1628 case RTFIO::eControlAtom_pard:
1629 return (HandleControlWord_pard (readerContext, controlWord));
1630 case RTFIO::eControlAtom_pc:
1631 return (HandleControlWord_pc (readerContext, controlWord));
1632 case RTFIO::eControlAtom_pca:
1633 return (HandleControlWord_pca (readerContext, controlWord));
1634 case RTFIO::eControlAtom_pict:
1635 return (HandleControlWord_pict (readerContext, controlWord));
1636 case RTFIO::eControlAtom_plain:
1637 return (HandleControlWord_plain (readerContext, controlWord));
1638 case RTFIO::eControlAtom_pntext:
1639 return (HandleControlWord_pntext (readerContext, controlWord));
1640 case RTFIO::eControlAtom_qc:
1641 return (HandleControlWord_qc (readerContext, controlWord));
1642 case RTFIO::eControlAtom_qj:
1643 return (HandleControlWord_qj (readerContext, controlWord));
1644 case RTFIO::eControlAtom_ql:
1645 return (HandleControlWord_ql (readerContext, controlWord));
1646 case RTFIO::eControlAtom_qr:
1647 return (HandleControlWord_qr (readerContext, controlWord));
1648 case RTFIO::eControlAtom_ri:
1649 return (HandleControlWord_ri (readerContext, controlWord));
1650 case RTFIO::eControlAtom_row:
1651 return (HandleControlWord_row (readerContext, controlWord));
1652 case RTFIO::eControlAtom_rtf:
1653 return (HandleControlWord_rtf (readerContext, controlWord));
1654 case RTFIO::eControlAtom_sa:
1655 return (HandleControlWord_sa (readerContext, controlWord));
1656 case RTFIO::eControlAtom_sb:
1657 return (HandleControlWord_sb (readerContext, controlWord));
1658 case RTFIO::eControlAtom_shad:
1659 return (HandleControlWord_shad (readerContext, controlWord));
1660 case RTFIO::eControlAtom_sl:
1661 return (HandleControlWord_sl (readerContext, controlWord));
1662 case RTFIO::eControlAtom_slmult:
1663 return (HandleControlWord_slmult (readerContext, controlWord));
1664 case RTFIO::eControlAtom_sub:
1665 return (HandleControlWord_sub (readerContext, controlWord));
1666 case RTFIO::eControlAtom_super:
1667 return (HandleControlWord_super (readerContext, controlWord));
1668 case RTFIO::eControlAtom_strike:
1669 return (HandleControlWord_strike (readerContext, controlWord));
1670 case RTFIO::eControlAtom_stylesheet:
1671 return (HandleControlWord_stylesheet (readerContext, controlWord));
1672 case RTFIO::eControlAtom_trgaph:
1673 return (HandleControlWord_trgaph (readerContext, controlWord));
1674 case RTFIO::eControlAtom_trleft:
1675 return (HandleControlWord_trleft (readerContext, controlWord));
1676 case RTFIO::eControlAtom_trowd:
1677 return (HandleControlWord_trowd (readerContext, controlWord));
1678 case RTFIO::eControlAtom_trpaddb:
1679 case RTFIO::eControlAtom_trpaddl:
1680 case RTFIO::eControlAtom_trpaddr:
1681 case RTFIO::eControlAtom_trpaddt:
1682 return (HandleControlWord_trpaddX (readerContext, controlWord));
1683 case RTFIO::eControlAtom_trspdb:
1684 case RTFIO::eControlAtom_trspdl:
1685 case RTFIO::eControlAtom_trspdr:
1686 case RTFIO::eControlAtom_trspdt:
1687 return (HandleControlWord_trspdX (readerContext, controlWord));
1688 case RTFIO::eControlAtom_tx:
1689 return (HandleControlWord_tx (readerContext, controlWord));
1690 case RTFIO::eControlAtom_u:
1691 return (HandleControlWord_u (readerContext, controlWord));
1692 case RTFIO::eControlAtom_uc:
1693 return (HandleControlWord_uc (readerContext, controlWord));
1694 case RTFIO::eControlAtom_ul:
1695 return (HandleControlWord_ul (readerContext, controlWord));
1696 case RTFIO::eControlAtom_ulnone:
1697 return (HandleControlWord_ulnone (readerContext, controlWord));
1698 case RTFIO::eControlAtom_up:
1699 return (HandleControlWord_up (readerContext, controlWord));
1700 case RTFIO::eControlAtom_v:
1701 return (HandleControlWord_v (readerContext, controlWord));
1702
1703 default:
1704 return (HandleControlWord_UnknownControlWord (readerContext, controlWord));
1705 }
1706}
1707
1708bool StyledTextIOReader_RTF::HandleControlWord_ansi (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
1709{
1710 readerContext.fDocumentCharacterSet = WellKnownCodePages::kANSI;
1711 readerContext.UseInputCharSetEncoding (readerContext.fDocumentCharacterSet);
1712 return false;
1713}
1714
1715bool StyledTextIOReader_RTF::HandleControlWord_author (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& /*controlWord*/)
1716{
1717 SkipToEndOfCurrentGroup (); // we ignore unknown groups
1718 return true;
1719}
1720
1721bool StyledTextIOReader_RTF::HandleControlWord_b (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
1722{
1723 ApplyFontSpec (readerContext, controlWord);
1724 return false;
1725}
1726
1727bool StyledTextIOReader_RTF::HandleControlWord_brdrXXX (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
1728{
1729 switch (controlWord.fWord) {
1730 case RTFIO::eControlAtom_brdrcf: {
1731 if (not controlWord.fHasArg) {
1732 HandleBadlyFormattedInput (true); // must have a numeric color-number argument
1733 }
1734 readerContext.GetDestination ().SetTableBorderColor (LookupColor (readerContext, static_cast<size_t> (controlWord.fValue)));
1735 } break;
1736 }
1737 // CHECK brdrw and brdrcf
1738 //CHECK FOR VARIOUS TABLE BORDER ATTRIBUTES
1739 //\brdrs
1740 //\brdrth
1741 //brdrtriple
1742
1743 return false;
1744}
1745
1746bool StyledTextIOReader_RTF::HandleControlWord_cchs (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
1747{
1748 CheckIfAboutToStartBody (readerContext);
1749 if (readerContext.GetCurrentGroupContext () == nullptr) {
1750 HandleBadlyFormattedInput (true); // cannot charset
1751 }
1752 if (not controlWord.fHasArg) {
1753 HandleBadlyFormattedInput (true); // must have a numeric font-size argument
1754 }
1755
1756 switch (controlWord.fValue) {
1757 case 0:
1758 readerContext.GetCurrentGroupContext ()->fCCHSCodePage = WellKnownCodePages::kANSI;
1759 break;
1760 case 254:
1761 readerContext.GetCurrentGroupContext ()->fCCHSCodePage = WellKnownCodePages::kPC;
1762 break;
1763 case 255:
1764 readerContext.GetCurrentGroupContext ()->fCCHSCodePage = WellKnownCodePages::kPCA;
1765 break;
1766 case 256:
1767 readerContext.GetCurrentGroupContext ()->fCCHSCodePage = WellKnownCodePages::kMAC;
1768 break;
1769 default: {
1770 // unsure what todo here. We could throw an error, but I suspect its probably best to
1771 // simply IGNORE the attempted code page setting.
1772 // ???? LGP 960903
1773 } break;
1774 }
1775 return false;
1776}
1777
1778bool StyledTextIOReader_RTF::HandleControlWord_cell (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
1779{
1780 CheckIfAboutToStartBody (readerContext);
1781 readerContext.GetDestination ().EndCell (true);
1782 return false;
1783}
1784
1785bool StyledTextIOReader_RTF::HandleControlWord_cellx (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
1786{
1787 CheckIfAboutToStartBody (readerContext);
1788 if (not controlWord.fHasArg) {
1789 HandleBadlyFormattedInput (true); // must have a numeric code page argument
1790 }
1791 readerContext.GetDestination ().SetCellX (TWIPS (controlWord.fValue));
1792 return false;
1793}
1794
1795bool StyledTextIOReader_RTF::HandleControlWord_cf (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
1796{
1797 ApplyFontSpec (readerContext, controlWord);
1798 return false;
1799}
1800
1801bool StyledTextIOReader_RTF::HandleControlWord_clcbpat (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
1802{
1803 CheckIfAboutToStartBody (readerContext);
1804 readerContext.GetDestination ().SetCellBackColor (LookupColor (readerContext, static_cast<size_t> (controlWord.fValue)));
1805 return false;
1806}
1807
1808bool StyledTextIOReader_RTF::HandleControlWord_cpg (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
1809{
1810 CheckIfAboutToStartBody (readerContext);
1811 if (readerContext.GetCurrentGroupContext () == nullptr) {
1812 HandleBadlyFormattedInput (true); // cannot charset
1813 }
1814 if (not controlWord.fHasArg) {
1815 HandleBadlyFormattedInput (true); // must have a numeric code page argument
1816 }
1817 readerContext.GetCurrentGroupContext ()->fCurrentCodePage = controlWord.fValue;
1818 readerContext.UseInputCharSetEncoding (readerContext.GetCurrentGroupContext ()->fCurrentCodePage);
1819 return false;
1820}
1821
1822bool StyledTextIOReader_RTF::HandleControlWord_colortbl (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
1823{
1824 if (readerContext.fColorTable != nullptr) {
1825 HandleBadlyFormattedInput (); // cannot have two color tables...
1826 }
1827
1828 /*
1829 * Now try to really read color table.
1830 *
1831 * Start just inside the group, and look for each sub-group. And assume each subgroup is
1832 * a font specification, and read it as such.
1833 */
1834 if (readerContext.GetCurrentGroupContext () == nullptr) {
1835 HandleBadlyFormattedInput (true);
1836 }
1837 GetSrcStream ().seek_to (readerContext.GetCurrentGroupContext ()->fCurrentGroupStartIdx);
1838 if (GetNextChar () != RTFIO::kRTFOpenGroupChar) {
1839 HandleBadlyFormattedInput (true);
1840 }
1841
1842 {
1843 RTFIO::ControlWord cword = ReadControlWord ();
1844 if (cword.fWord != RTFIO::eControlAtom_colortbl or cword.fHasArg) {
1845 HandleBadlyFormattedInput (true);
1846 }
1847 }
1848
1849 vector<Color> colorTable;
1850 while (true) {
1851 Color curColor = Color::kBlack;
1852
1853 // Read \\redN
1854 ReadRed:
1855 switch (PeekNextChar ()) {
1856 case RTFIO::kRTFCloseGroupChar: {
1857 ConsumeNextChar ();
1858 goto ColorsComplete;
1859 } break;
1860 case ';': {
1861 ConsumeNextChar ();
1862 goto ColorComplete;
1863 } break;
1864 case RTFIO::kRTFStartTagChar: {
1865 RTFIO::ControlWord cword = ReadControlWord ();
1866 if (cword.fWord == RTFIO::eControlAtom_red) {
1867 if (not cword.fHasArg) {
1868 HandleBadlyFormattedInput ();
1869 }
1870 else {
1871 curColor = Color (static_cast<Color::ColorValue> (unsigned (cword.fValue) << 8), 0, 0);
1872 }
1873 }
1874 else {
1875 HandleBadlyFormattedInput ();
1876 }
1877 } break;
1878 case '\r':
1879 case '\n':
1880 case ' ': {
1881 ConsumeNextChar ();
1882 goto ReadRed;
1883 } break;
1884 default: {
1885 HandleBadlyFormattedInput ();
1886 }
1887 }
1888
1889 // Read \\greenN
1890 ReadGreen:
1891 switch (PeekNextChar ()) {
1892 case ';': {
1893 ConsumeNextChar ();
1894 goto ColorComplete;
1895 } break;
1896 case RTFIO::kRTFStartTagChar: {
1897 RTFIO::ControlWord cword = ReadControlWord ();
1898 if (cword.fWord == RTFIO::eControlAtom_green) {
1899 if (not cword.fHasArg) {
1900 HandleBadlyFormattedInput ();
1901 }
1902 else {
1903 curColor = Color (curColor.GetRed (), static_cast<Color::ColorValue> (unsigned (cword.fValue) << 8), 0);
1904 }
1905 }
1906 else {
1907 HandleBadlyFormattedInput ();
1908 }
1909 } break;
1910 case '\r':
1911 case '\n':
1912 case ' ': {
1913 ConsumeNextChar ();
1914 goto ReadGreen;
1915 } break;
1916 default: {
1917 HandleBadlyFormattedInput ();
1918 }
1919 }
1920
1921 // Read \\blueN
1922 ReadBlue:
1923 switch (PeekNextChar ()) {
1924 case ';': {
1925 ConsumeNextChar ();
1926 goto ColorComplete;
1927 } break;
1928 case RTFIO::kRTFStartTagChar: {
1929 RTFIO::ControlWord cword = ReadControlWord ();
1930 if (cword.fWord == RTFIO::eControlAtom_blue) {
1931 if (not cword.fHasArg) {
1932 HandleBadlyFormattedInput ();
1933 }
1934 else {
1935 curColor = Color (curColor.GetRed (), curColor.GetGreen (), static_cast<Color::ColorValue> (unsigned (cword.fValue) << 8));
1936 }
1937 }
1938 else {
1939 HandleBadlyFormattedInput ();
1940 }
1941 } break;
1942 case '\r':
1943 case '\n':
1944 case ' ': {
1945 ConsumeNextChar ();
1946 goto ReadBlue;
1947 } break;
1948 default: {
1949 HandleBadlyFormattedInput ();
1950 }
1951 }
1952
1953 // Read ';'
1954 ReadSemiColon:
1955 switch (PeekNextChar ()) {
1956 case ';': {
1957 ConsumeNextChar ();
1958 goto ColorComplete;
1959 } break;
1960 case ' ': {
1961 ConsumeNextChar ();
1962 goto ReadSemiColon;
1963 } break;
1964 default: {
1965 HandleBadlyFormattedInput ();
1966 ConsumeNextChar (); // in case the above doesn't throw - don't get caught in an infinite loop...
1967 }
1968 }
1969
1970 ColorComplete:
1971 colorTable.push_back (curColor);
1972 }
1973
1974ColorsComplete:
1975 Assert (readerContext.fColorTable == nullptr);
1976 readerContext.fColorTable = new RTFIO::ColorTable (colorTable);
1977
1978 return true;
1979}
1980
1981bool StyledTextIOReader_RTF::HandleControlWord_deff (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
1982{
1983 if (not controlWord.fHasArg) {
1984 HandleBadlyFormattedInput ();
1985 }
1986 else {
1987 readerContext.fDefaultFontNumber = controlWord.fValue;
1988 }
1989 return false;
1990}
1991
1992bool StyledTextIOReader_RTF::HandleControlWord_deftab (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& controlWord)
1993{
1994 if (not controlWord.fHasArg) {
1995 HandleBadlyFormattedInput ();
1996 return false;
1997 }
1998 if (controlWord.fValue < 0) {
1999 HandleBadlyFormattedInput ();
2000 return false;
2001 }
2002 TWIPS tsl = TWIPS (controlWord.fValue);
2003 if (tsl == 0) {
2004 HandleBadlyFormattedInput ();
2005 return false;
2006 }
2007 if (fRTFInfo != nullptr) {
2008 fRTFInfo->fDefaultTabStop = tsl;
2009 }
2010 return false;
2011}
2012
2013bool StyledTextIOReader_RTF::HandleControlWord_deleted (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& /*controlWord*/)
2014{
2015 /*
2016 * Groups marked as deleted, we should not read in any of the text. Eventually, we
2017 * might want to keep track of this stuff if we ever supported changebars etc.
2018 */
2019 SkipToEndOfCurrentGroup ();
2020 return true;
2021}
2022
2023bool StyledTextIOReader_RTF::HandleControlWord_dn (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2024{
2025 /*
2026 * Kludge/workaround for SPR#1620.
2027 */
2028 if (controlWord.fHasArg and controlWord.fValue > 0) {
2029 RTFIO::ControlWord newCW;
2030 newCW.fHasArg = false;
2031 newCW.fWord = RTFIO::eControlAtom_sub;
2032 return HandleControlWord_sub (readerContext, newCW);
2033 }
2034 return false;
2035}
2036
2037bool StyledTextIOReader_RTF::HandleControlWord_f (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2038{
2039 ApplyFontSpec (readerContext, controlWord);
2040 return false;
2041}
2042
2043bool StyledTextIOReader_RTF::HandleControlWord_fi (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2044{
2045 if (not controlWord.fHasArg) {
2046 HandleBadlyFormattedInput (); // must have a numeric argument
2047 }
2048 else {
2049 readerContext.GetDestination ().SetFirstIndent (readerContext.GetCurrentGroupContext ()->fDestinationContext.fFirstIndent =
2050 TWIPS (controlWord.fValue));
2051 }
2052 return false;
2053}
2054
2055bool StyledTextIOReader_RTF::HandleControlWord_footer (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& /*controlWord*/)
2056{
2057 /*
2058 * We ignore these for now. In Led 2.3 - we would treat them as plain text - part of the document. At least then you would SEE
2059 * them. But they came out in the wrong place and looked terrible. On balance - this is probably better.
2060 */
2061 SkipToEndOfCurrentGroup ();
2062 return true;
2063}
2064
2065bool StyledTextIOReader_RTF::HandleControlWord_fonttbl (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2066{
2067 if (readerContext.fFontTable != nullptr) {
2068 HandleBadlyFormattedInput (); // cannot have two font tables...
2069 }
2070
2071 /*
2072 * Now try to really read font table.
2073 *
2074 * Start just inside the group, and look for each sub-group. And assume each subgroup is
2075 * a font specification, and read it as such.
2076 */
2077 if (readerContext.GetCurrentGroupContext () == nullptr) {
2078 HandleBadlyFormattedInput (true);
2079 }
2080 GetSrcStream ().seek_to (readerContext.GetCurrentGroupContext ()->fCurrentGroupStartIdx);
2081 if (GetNextChar () != RTFIO::kRTFOpenGroupChar) {
2082 HandleBadlyFormattedInput (true);
2083 }
2084
2085 vector<FontTableEntry> fontTable;
2086
2087 while (true) {
2088 ScanForwardFor ("{}");
2089 switch (PeekNextChar ()) {
2090 case RTFIO::kRTFOpenGroupChar: {
2091 fontTable.push_back (ReadInFontTablesEntry ());
2092 } break;
2093 case RTFIO::kRTFCloseGroupChar: {
2094 ConsumeNextChar ();
2095 if (readerContext.fFontTable == nullptr) {
2096 // ignore second font table - if there are multiples and we didn't throw out of here...
2097 readerContext.fFontTable = new FontTable (fontTable);
2098 }
2099 return true; // ALL DONE
2100 } break;
2101 default: {
2102 Assert (false);
2103 } break;
2104 }
2105 }
2106 return true;
2107}
2108
2109bool StyledTextIOReader_RTF::HandleControlWord_fs (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2110{
2111 ApplyFontSpec (readerContext, controlWord);
2112 return false;
2113}
2114
2115bool StyledTextIOReader_RTF::HandleControlWord_header (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& /*controlWord*/)
2116{
2117 /*
2118 * We ignore these for now. In Led 2.3 - we would treat them as plain text - part of the document. At least then you would SEE
2119 * them. But they came out in the wrong place and looked terrible. On balance - this is probably better.
2120 */
2121 SkipToEndOfCurrentGroup ();
2122 return true;
2123}
2124
2125bool StyledTextIOReader_RTF::HandleControlWord_i (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2126{
2127 ApplyFontSpec (readerContext, controlWord);
2128 return false;
2129}
2130
2131bool StyledTextIOReader_RTF::HandleControlWord_ilvl (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2132{
2133 if (not controlWord.fHasArg) {
2134 HandleBadlyFormattedInput (); // must have a numeric argument
2135 }
2136 else {
2137 unsigned char listLevel = static_cast<unsigned char> (controlWord.fValue);
2138 if (listLevel > 8) {
2139 listLevel = 0;
2140 }
2141 readerContext.GetDestination ().SetListIndentLevel (readerContext.GetCurrentGroupContext ()->fDestinationContext.fListIndentLevel = listLevel);
2142 }
2143 return false;
2144}
2145
2146bool StyledTextIOReader_RTF::HandleControlWord_info (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& /*controlWord*/)
2147{
2148 SkipToEndOfCurrentGroup (); // we ignore unknown groups
2149 return true;
2150}
2151
2152bool StyledTextIOReader_RTF::HandleControlWord_intbl (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2153{
2154 CheckIfAboutToStartBody (readerContext);
2155 if (readerContext.GetCurrentGroupContext () == nullptr) {
2156 HandleBadlyFormattedInput (true); // cannot set INTABLE without a current group!
2157 }
2158 readerContext.GetDestination ().SetInTable (true);
2159 return false;
2160}
2161
2162bool StyledTextIOReader_RTF::HandleControlWord_li (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2163{
2164 if (not controlWord.fHasArg) {
2165 HandleBadlyFormattedInput (); // must have a numeric argument
2166 }
2167 else {
2168 readerContext.GetDestination ().SetLeftMargin (readerContext.GetCurrentGroupContext ()->fDestinationContext.fLeftMargin =
2169 TWIPS (controlWord.fValue));
2170 }
2171 return false;
2172}
2173
2174bool StyledTextIOReader_RTF::HandleControlWord_line (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2175{
2176 readerContext.GetDestination ().AppendSoftLineBreak ();
2177 return false;
2178}
2179
2180bool StyledTextIOReader_RTF::HandleControlWord_listtext (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& /*controlWord*/)
2181{
2182 SkipToEndOfCurrentGroup (); // we ignore this because we properly handle the lists already
2183 return true;
2184}
2185
2186bool StyledTextIOReader_RTF::HandleControlWord_ls (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2187{
2188 if (not controlWord.fHasArg) {
2189 HandleBadlyFormattedInput (); // must have a numeric argument
2190 }
2191 else {
2192 /*
2193 * TmpHack - really should read this style from the listtable based on given ID
2194 */
2195 ListStyle listStyle = eListStyle_Bullet;
2196 readerContext.GetDestination ().SetListStyle (readerContext.GetCurrentGroupContext ()->fDestinationContext.fListStyle = listStyle);
2197 }
2198 return false;
2199}
2200
2201bool StyledTextIOReader_RTF::HandleControlWord_mac (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2202{
2203 readerContext.fDocumentCharacterSet = WellKnownCodePages::kMAC;
2204 readerContext.UseInputCharSetEncoding (readerContext.fDocumentCharacterSet);
2205 return false;
2206}
2207
2208bool StyledTextIOReader_RTF::HandleControlWord_margX (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& controlWord)
2209{
2210 if (not controlWord.fHasArg) {
2211 HandleBadlyFormattedInput (); // must have a numeric argument
2212 return false;
2213 }
2214 AssertNotNull (fRTFInfo);
2215 switch (controlWord.fWord) {
2216 case RTFIO::eControlAtom_margt: {
2217 fRTFInfo->fDefaultMarginTop = TWIPS (controlWord.fValue);
2218 } break;
2219 case RTFIO::eControlAtom_margb: {
2220 fRTFInfo->fDefaultMarginBottom = TWIPS (controlWord.fValue);
2221 } break;
2222 case RTFIO::eControlAtom_margl: {
2223 fRTFInfo->fDefaultMarginLeft = TWIPS (controlWord.fValue);
2224 } break;
2225 case RTFIO::eControlAtom_margr: {
2226 fRTFInfo->fDefaultMarginRight = TWIPS (controlWord.fValue);
2227 } break;
2228 default: {
2229 Assert (false);
2230 }
2231 }
2232 return false;
2233}
2234
2235bool StyledTextIOReader_RTF::HandleControlWord_outl (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2236{
2237 ApplyFontSpec (readerContext, controlWord);
2238 return false;
2239}
2240
2241bool StyledTextIOReader_RTF::HandleControlWord_paperX (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& controlWord)
2242{
2243 if (not controlWord.fHasArg) {
2244 HandleBadlyFormattedInput (); // must have a numeric argument
2245 return false;
2246 }
2247 AssertNotNull (fRTFInfo);
2248 switch (controlWord.fWord) {
2249 case RTFIO::eControlAtom_paperh: {
2250 fRTFInfo->fDefaultPaperSize.v = TWIPS (controlWord.fValue);
2251 } break;
2252 case RTFIO::eControlAtom_paperw: {
2253 fRTFInfo->fDefaultPaperSize.h = TWIPS (controlWord.fValue);
2254 } break;
2255 default: {
2256 Assert (false);
2257 }
2258 }
2259 return false;
2260}
2261
2262bool StyledTextIOReader_RTF::HandleControlWord_object (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2263{
2264 CheckIfAboutToStartBody (readerContext);
2265#if qStroika_Frameworks_Led_SupportGDI
2266 using UnknownRTFEmbedding = RTFIO::UnknownRTFEmbedding;
2267#endif
2268 using ControlWord = RTFIO::ControlWord;
2269 /*
2270 * Now try to really read on object in. Could be a Led-private-format object. Could be an OLE object. Or one of many
2271 * types we don't support.
2272 *
2273 * For ones we don't support, we SHOULD use the \result tag - if any - to display the thing reasonably. But for now,
2274 * we don't support that. We simply ignore \result tags (see RTF spec for details).
2275 */
2276
2277 /*
2278 * After the \object tag comes the <objtype>. If this is \objemb, then we have an OLE ombedding, and we'll try to read
2279 * that in. If its \ledprivateobjectembeddingformat, then we will try to read that. Anything else, we don't understand, and
2280 * we will try to read it is an 'UnknownRTFEmbedding'.
2281 */
2282 bool isOLEEmbedding = false;
2283 bool isPrivateLedEmbedding = false;
2284 TWIPS_Point shownSize = TWIPS_Point (TWIPS{0}, TWIPS{0});
2285 vector<char> objData;
2286 float scaleX = 1.0f;
2287 float scaleY = 1.0f;
2288 size_t resultFoundAt = size_t (-1);
2289
2290 while (true) {
2291 // Look for either control words, or groups
2292 if (PeekNextChar () != RTFIO::kRTFStartTagChar) {
2293 ScanForwardFor ("{}");
2294 }
2295 switch (PeekNextChar ()) {
2296 case RTFIO::kRTFStartTagChar: {
2297 ControlWord cw = ReadControlWord ();
2298 switch (cw.fWord) {
2299 case RTFIO::eControlAtom_ledprivateobjectembeddingformat:
2300 isPrivateLedEmbedding = true;
2301 break;
2302 case RTFIO::eControlAtom_objemb:
2303 isOLEEmbedding = true;
2304 break;
2305 case RTFIO::eControlAtom_objh:
2306 if (not cw.fHasArg) {
2307 HandleBadlyFormattedInput ();
2308 break;
2309 };
2310 shownSize.v = TWIPS (cw.fValue);
2311 break;
2312 case RTFIO::eControlAtom_objw:
2313 if (not cw.fHasArg) {
2314 HandleBadlyFormattedInput ();
2315 break;
2316 };
2317 shownSize.h = TWIPS (cw.fValue);
2318 break;
2319 case RTFIO::eControlAtom_objscalex:
2320 if (not cw.fHasArg) {
2321 HandleBadlyFormattedInput ();
2322 break;
2323 };
2324 scaleX = cw.fValue / 100.0f;
2325 break;
2326 case RTFIO::eControlAtom_objscaley:
2327 if (not cw.fHasArg) {
2328 HandleBadlyFormattedInput ();
2329 break;
2330 };
2331 scaleY = cw.fValue / 100.0f;
2332 break;
2333 case RTFIO::eControlAtom_result:
2334 resultFoundAt = readerContext.GetReader ().GetSrcStream ().current_offset ();
2335 break;
2336 }
2337 } break;
2338 case RTFIO::kRTFOpenGroupChar: {
2339 ReadInObjectSubGroupEntry (readerContext, &objData, &resultFoundAt);
2340 } break;
2341 case RTFIO::kRTFCloseGroupChar: {
2342 /*
2343 * Sanity check shown size.
2344 */
2345 shownSize.h *= scaleX;
2346 shownSize.v *= scaleY;
2347#if qStroika_Frameworks_Led_SupportGDI
2348 if (shownSize.v > 20000 or shownSize.h > 20000 or shownSize.h < 100 or shownSize.v < 100) {
2349 shownSize = UnknownRTFEmbedding::CalcStaticDefaultShownSize ();
2350 }
2351#endif
2352 /*
2353 * First, try to construct the specific kind of object using the info we've extracted.
2354 */
2355 if (isOLEEmbedding and isPrivateLedEmbedding) {
2356 HandleBadlyFormattedInput ();
2357 }
2358 if (isOLEEmbedding) {
2359 try {
2360 ConstructOLEEmebddingFromRTFInfo (readerContext, shownSize, objData.size (), &objData.front ());
2361 }
2362 catch (...) {
2363 isOLEEmbedding = false;
2364 }
2365 }
2366 if (isPrivateLedEmbedding) {
2367 try {
2368 ConstructLedEmebddingFromRTFInfo (readerContext, objData.size (), &objData.front ());
2369 }
2370 catch (...) {
2371 isPrivateLedEmbedding = false;
2372 }
2373 }
2374
2375 /*
2376 * If we succeded, cleanup, and we're done.
2377 */
2378 if (isOLEEmbedding or isPrivateLedEmbedding) {
2379 ConsumeNextChar ();
2380 }
2381 else {
2382 // make a fake-embedding object for what we couldn't read in.
2383 GetSrcStream ().seek_to (readerContext.GetCurrentGroupContext ()->fCurrentGroupStartIdx);
2384 string s = ReadInGroupAndSave ();
2385
2386#if qStroika_Frameworks_Led_SupportGDI
2387 SimpleEmbeddedObjectStyleMarker* embedding = nullptr;
2388
2389 /*
2390 * If there is a result tag and a PICT we can read in it - great. Use that as an arg to the UnknownRTFEmbedding ().
2391 * Otherwise - create one without the DIB/PICT.
2392 */
2393 if (resultFoundAt != size_t (-1)) {
2394 size_t RETURN_TO = readerContext.GetReader ().GetSrcStream ().current_offset ();
2395 GetSrcStream ().seek_to (resultFoundAt);
2396 if (SearchForwardFor ("\\pict", s.length ())) {
2397 ControlWord cw = ReadControlWord ();
2398 if (cw.fWord == RTFIO::eControlAtom_pict) {
2399 TWIPS_Point bmSize = TWIPS_Point (TWIPS{0}, TWIPS{0});
2400 vector<char> pictureData;
2401 ImageFormat imageFormat = eDefaultImageFormat;
2402 ReadTopLevelPictData (&shownSize, &imageFormat, &bmSize, &pictureData);
2403 /*
2404 * For now - simply convert whatever sort of data it is to DIB data, and then create a DIB embedding.
2405 * In the future - we may want to keep the original Data - in some cases - like for enhanced-meta-files etc. Then here - read the data another way,
2406 * and convert another sort of embedding object. The reason we might want to make that change is that the conversion from
2407 * meta-file data to DIB data can be lossy, and can make the data size much larger - both bad things.
2408 * --LGP 2000-07-08
2409 */
2410 unique_ptr<Led_DIB> dib = unique_ptr<Led_DIB> (
2411 ConstructDIBFromData (shownSize, imageFormat, bmSize, objData.size (), &pictureData.front ()));
2412 if (dib.get () != nullptr) {
2413 RTFIO::UnknownRTFEmbedding* e = new RTFIO::UnknownRTFEmbedding (RTFIO::kRTFBodyGroupFragmentClipFormat,
2414 RTFIO::kRTFBodyGroupFragmentEmbeddingTag,
2415 s.c_str (), s.length (), dib.get ());
2416 e->SetShownSize (shownSize);
2417 embedding = e;
2418 }
2419 }
2420 }
2421 GetSrcStream ().seek_to (RETURN_TO);
2422 }
2423
2424 if (embedding == nullptr) {
2425 RTFIO::UnknownRTFEmbedding* e = new RTFIO::UnknownRTFEmbedding (
2426 RTFIO::kRTFBodyGroupFragmentClipFormat, RTFIO::kRTFBodyGroupFragmentEmbeddingTag, s.c_str (), s.length ());
2427 e->SetShownSize (shownSize);
2428 embedding = e;
2429 }
2430 try {
2431 readerContext.GetDestination ().AppendEmbedding (embedding);
2432 }
2433 catch (...) {
2434 delete embedding;
2435 throw;
2436 }
2437#endif
2438 }
2439 return true; // ALL DONE
2440 } break;
2441 default: {
2442 Assert (false);
2443 } break;
2444 }
2445 }
2446 return true;
2447}
2448
2449bool StyledTextIOReader_RTF::HandleControlWord_par (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2450{
2451 /*
2452 * According to RTF spec, \par ENDS a paragraph. This very nearly is accomplished by just emitting a newline in Led. But -
2453 * for the special case of the last paragraph in a document - that doesn't work. In that case - Led would produce an extra
2454 * empty paragraph at the end of the document (SinkStream output). So - we must be more careful.
2455 */
2456 CheckIfAboutToStartBody (readerContext);
2457 readerContext.GetDestination ().EndParagraph ();
2458 return false;
2459}
2460
2461bool StyledTextIOReader_RTF::HandleControlWord_pard (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2462{
2463 // Reset ALL defaults here
2464 // Assign to current context AND destination at the same time...
2465 readerContext.GetDestination ().SetTabStops (readerContext.GetCurrentGroupContext ()->fDestinationContext.fTabStops =
2466 StandardTabStopList (GetRTFInfo ().GetDefaultTabStop ()));
2467 readerContext.GetDestination ().SetJustification (readerContext.GetCurrentGroupContext ()->fDestinationContext.fJustification = eLeftJustify);
2468 readerContext.GetDestination ().SetFirstIndent (readerContext.GetCurrentGroupContext ()->fDestinationContext.fFirstIndent = TWIPS{0});
2469 readerContext.GetDestination ().SetLeftMargin (readerContext.GetCurrentGroupContext ()->fDestinationContext.fLeftMargin = TWIPS{0});
2470 readerContext.GetDestination ().SetRightMargin (readerContext.GetCurrentGroupContext ()->fDestinationContext.fRightMargin = TWIPS{0});
2471 readerContext.GetDestination ().SetSpaceBefore (readerContext.GetCurrentGroupContext ()->fDestinationContext.fSpaceBefore = TWIPS{0});
2472 readerContext.GetDestination ().SetSpaceAfter (readerContext.GetCurrentGroupContext ()->fDestinationContext.fSpaceAfter = TWIPS{0});
2473 readerContext.GetDestination ().SetSpaceBetweenLines (readerContext.GetCurrentGroupContext ()->fDestinationContext.fSpaceBetweenLines = 1000);
2474 readerContext.GetDestination ().SetSpaceBetweenLinesMult (readerContext.GetCurrentGroupContext ()->fDestinationContext.fSpaceBetweenLinesMult = true);
2475 readerContext.GetDestination ().SetListStyle (readerContext.GetCurrentGroupContext ()->fDestinationContext.fListStyle = eListStyle_None);
2476 readerContext.GetDestination ().SetListIndentLevel (readerContext.GetCurrentGroupContext ()->fDestinationContext.fListIndentLevel = 0);
2477
2478 // as part of debugging SPR#1406- I saw that \pard should be interpretted as resetting this flag to off
2479 // test carefully that doesn't break anything...
2480 readerContext.GetDestination ().SetInTable (false);
2481 return false;
2482}
2483
2484bool StyledTextIOReader_RTF::HandleControlWord_pc (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2485{
2486 readerContext.fDocumentCharacterSet = WellKnownCodePages::kPC;
2487 readerContext.UseInputCharSetEncoding (readerContext.fDocumentCharacterSet);
2488 return false;
2489}
2490
2491bool StyledTextIOReader_RTF::HandleControlWord_pca (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2492{
2493 readerContext.fDocumentCharacterSet = WellKnownCodePages::kPCA;
2494 readerContext.UseInputCharSetEncoding (readerContext.fDocumentCharacterSet);
2495 return false;
2496}
2497
2498bool StyledTextIOReader_RTF::HandleControlWord_pict (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2499{
2500 CheckIfAboutToStartBody (readerContext);
2501
2502 TWIPS_Point shownSize = TWIPS_Point (TWIPS{0}, TWIPS{0});
2503 TWIPS_Point bmSize = TWIPS_Point (TWIPS{0}, TWIPS{0});
2504 vector<char> objData;
2505 ImageFormat imageFormat = eDefaultImageFormat;
2506 ReadTopLevelPictData (&shownSize, &imageFormat, &bmSize, &objData);
2507
2508 /*
2509 * For now - simply convert whatever sort of data it is to DIB data, and then create a DIB embedding.
2510 * In the future - we may want to keep the original Data - in some cases - like for enhanced-meta-files etc. Then here - read the data another way,
2511 * and convert another sort of embedding object. The reason we might want to make that change is that the conversion from
2512 * meta-file data to DIB data can be lossy, and can make the data size much larger - both bad things.
2513 * --LGP 2000-07-08
2514 */
2515 unique_ptr<Led_DIB> dib = unique_ptr<Led_DIB> (ConstructDIBFromData (shownSize, imageFormat, bmSize, objData.size (), &objData.front ()));
2516#if qStroika_Frameworks_Led_SupportGDI
2517 bool createSucceeded = dib.get () != nullptr;
2518 SimpleEmbeddedObjectStyleMarker* embedding = nullptr;
2519 if (createSucceeded) {
2520 embedding = new StandardDIBStyleMarker (dib.get ());
2521 }
2522 else {
2523 // make a fake-embedding object for what we couldn't read in.
2524 GetSrcStream ().seek_to (readerContext.GetCurrentGroupContext ()->fCurrentGroupStartIdx);
2525 string s = ReadInGroupAndSave ();
2526 embedding = new RTFIO::UnknownRTFEmbedding (RTFIO::kRTFBodyGroupFragmentClipFormat, RTFIO::kRTFBodyGroupFragmentEmbeddingTag,
2527 s.c_str (), s.length ());
2528 }
2529
2530 if (embedding != nullptr) {
2531 try {
2532 readerContext.GetDestination ().AppendEmbedding (embedding);
2533 }
2534 catch (...) {
2535 delete embedding;
2536 throw;
2537 }
2538 }
2539#endif
2540 return true;
2541}
2542
2543bool StyledTextIOReader_RTF::HandleControlWord_plain (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2544{
2545 ApplyFontSpec (readerContext, controlWord);
2546 return false;
2547}
2548
2549bool StyledTextIOReader_RTF::HandleControlWord_pntext (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& /*controlWord*/)
2550{
2551 SkipToEndOfCurrentGroup (); // we ignore pntext groups cuz we get the \lsN stuff instead... (spr#968)
2552 return true;
2553}
2554
2555bool StyledTextIOReader_RTF::HandleControlWord_qc (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2556{
2557 readerContext.GetDestination ().SetJustification (readerContext.GetCurrentGroupContext ()->fDestinationContext.fJustification = eCenterJustify);
2558 return false;
2559}
2560
2561bool StyledTextIOReader_RTF::HandleControlWord_qj (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2562{
2563 readerContext.GetDestination ().SetJustification (readerContext.GetCurrentGroupContext ()->fDestinationContext.fJustification = eFullyJustify);
2564 return false;
2565}
2566
2567bool StyledTextIOReader_RTF::HandleControlWord_ql (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2568{
2569 readerContext.GetDestination ().SetJustification (readerContext.GetCurrentGroupContext ()->fDestinationContext.fJustification = eLeftJustify);
2570 return false;
2571}
2572
2573bool StyledTextIOReader_RTF::HandleControlWord_qr (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2574{
2575 readerContext.GetDestination ().SetJustification (readerContext.GetCurrentGroupContext ()->fDestinationContext.fJustification = eRightJustify);
2576 return false;
2577}
2578
2579bool StyledTextIOReader_RTF::HandleControlWord_ri (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2580{
2581 if (not controlWord.fHasArg) {
2582 HandleBadlyFormattedInput (); // must have a numeric argument
2583 }
2584 else {
2585 readerContext.GetDestination ().SetRightMargin (readerContext.GetCurrentGroupContext ()->fDestinationContext.fRightMargin =
2586 TWIPS (controlWord.fValue));
2587 }
2588 return false;
2589}
2590
2591bool StyledTextIOReader_RTF::HandleControlWord_row (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2592{
2593 CheckIfAboutToStartBody (readerContext);
2594 readerContext.GetDestination ().EndRow (true);
2595 return false;
2596}
2597
2598bool StyledTextIOReader_RTF::HandleControlWord_rtf (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& /*controlWord*/)
2599{
2600 // maybe should check version# or something - check only occurs once? No point...
2601 return false;
2602}
2603
2604bool StyledTextIOReader_RTF::HandleControlWord_sa (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2605{
2606 if (not controlWord.fHasArg) {
2607 HandleBadlyFormattedInput (); // must have a numeric argument
2608 }
2609 else {
2610 readerContext.GetDestination ().SetSpaceAfter (readerContext.GetCurrentGroupContext ()->fDestinationContext.fSpaceAfter =
2611 TWIPS (controlWord.fValue));
2612 }
2613 return false;
2614}
2615
2616bool StyledTextIOReader_RTF::HandleControlWord_sb (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2617{
2618 if (not controlWord.fHasArg) {
2619 HandleBadlyFormattedInput (); // must have a numeric argument
2620 }
2621 else {
2622 readerContext.GetDestination ().SetSpaceBefore (readerContext.GetCurrentGroupContext ()->fDestinationContext.fSpaceBefore =
2623 TWIPS (controlWord.fValue));
2624 }
2625 return false;
2626}
2627
2628bool StyledTextIOReader_RTF::HandleControlWord_shad (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2629{
2630 ApplyFontSpec (readerContext, controlWord);
2631 return false;
2632}
2633
2634bool StyledTextIOReader_RTF::HandleControlWord_sl (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2635{
2636 if (not controlWord.fHasArg) {
2637 HandleBadlyFormattedInput (); // must have a numeric argument
2638 }
2639 else {
2640 readerContext.GetDestination ().SetSpaceBetweenLines (readerContext.GetCurrentGroupContext ()->fDestinationContext.fSpaceBetweenLines =
2641 controlWord.fValue);
2642 }
2643 return false;
2644}
2645
2646bool StyledTextIOReader_RTF::HandleControlWord_slmult (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2647{
2648 if (not controlWord.fHasArg) {
2649 HandleBadlyFormattedInput (); // must have a numeric argument
2650 }
2651 else {
2652 readerContext.GetDestination ().SetSpaceBetweenLinesMult (
2653 readerContext.GetCurrentGroupContext ()->fDestinationContext.fSpaceBetweenLinesMult = static_cast<bool> (controlWord.fValue));
2654 }
2655 return false;
2656}
2657
2658bool StyledTextIOReader_RTF::HandleControlWord_sub (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2659{
2660 ApplyFontSpec (readerContext, controlWord);
2661 return false;
2662}
2663
2664bool StyledTextIOReader_RTF::HandleControlWord_super (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2665{
2666 ApplyFontSpec (readerContext, controlWord);
2667 return false;
2668}
2669
2670bool StyledTextIOReader_RTF::HandleControlWord_strike (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2671{
2672 ApplyFontSpec (readerContext, controlWord);
2673 return false;
2674}
2675
2676bool StyledTextIOReader_RTF::HandleControlWord_stylesheet (ReaderContext& /*readerContext*/, const RTFIO::ControlWord& /*controlWord*/)
2677{
2678 SkipToEndOfCurrentGroup (); // we ignore unknown groups
2679 return true;
2680}
2681
2682bool StyledTextIOReader_RTF::HandleControlWord_trleft (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2683{
2684 CheckIfAboutToStartBody (readerContext);
2685 if (not controlWord.fHasArg) {
2686 HandleBadlyFormattedInput (); // must have a numeric color-number argument
2687 }
2688 readerContext.GetDestination ().Set_trleft (TWIPS (controlWord.fValue));
2689 return false;
2690}
2691
2692bool StyledTextIOReader_RTF::HandleControlWord_trgaph (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2693{
2694 CheckIfAboutToStartBody (readerContext);
2695 if (not controlWord.fHasArg) {
2696 HandleBadlyFormattedInput (); // must have a numeric color-number argument
2697 }
2698 TWIPS margins = TWIPS (controlWord.fValue);
2699 readerContext.GetDestination ().SetDefaultCellMarginsForRow_top (margins);
2700 readerContext.GetDestination ().SetDefaultCellMarginsForRow_left (margins);
2701 readerContext.GetDestination ().SetDefaultCellMarginsForRow_bottom (margins);
2702 readerContext.GetDestination ().SetDefaultCellMarginsForRow_right (margins);
2703 return false;
2704}
2705
2706bool StyledTextIOReader_RTF::HandleControlWord_trowd (ReaderContext& readerContext, const RTFIO::ControlWord& /*controlWord*/)
2707{
2708 CheckIfAboutToStartBody (readerContext);
2709 readerContext.GetDestination ().Call_trowd ();
2710 return false;
2711}
2712
2713bool StyledTextIOReader_RTF::HandleControlWord_trpaddX (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2714{
2715 CheckIfAboutToStartBody (readerContext);
2716 if (not controlWord.fHasArg) {
2717 HandleBadlyFormattedInput (); // must have a numeric color-number argument
2718 }
2719 TWIPS margin = TWIPS (controlWord.fValue);
2720 switch (controlWord.fWord) {
2721 case RTFIO::eControlAtom_trpaddb:
2722 readerContext.GetDestination ().SetDefaultCellMarginsForRow_bottom (margin);
2723 break;
2724 case RTFIO::eControlAtom_trpaddl:
2725 readerContext.GetDestination ().SetDefaultCellMarginsForRow_left (margin);
2726 break;
2727 case RTFIO::eControlAtom_trpaddr:
2728 readerContext.GetDestination ().SetDefaultCellMarginsForRow_right (margin);
2729 break;
2730 case RTFIO::eControlAtom_trpaddt:
2731 readerContext.GetDestination ().SetDefaultCellMarginsForRow_top (margin);
2732 break;
2733 default:
2734 Assert (false); // NOT REACHED
2735 }
2736 return false;
2737}
2738
2739bool StyledTextIOReader_RTF::HandleControlWord_trspdX (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2740{
2741 CheckIfAboutToStartBody (readerContext);
2742 if (not controlWord.fHasArg) {
2743 HandleBadlyFormattedInput (); // must have a numeric color-number argument
2744 }
2745 TWIPS margin = TWIPS (controlWord.fValue);
2746 switch (controlWord.fWord) {
2747 case RTFIO::eControlAtom_trspdb:
2748 readerContext.GetDestination ().SetDefaultCellSpacingForRow_bottom (margin);
2749 break;
2750 case RTFIO::eControlAtom_trspdl:
2751 readerContext.GetDestination ().SetDefaultCellSpacingForRow_left (margin);
2752 break;
2753 case RTFIO::eControlAtom_trspdr:
2754 readerContext.GetDestination ().SetDefaultCellSpacingForRow_right (margin);
2755 break;
2756 case RTFIO::eControlAtom_trspdt:
2757 readerContext.GetDestination ().SetDefaultCellSpacingForRow_top (margin);
2758 break;
2759 default:
2760 Assert (false); // NOT REACHED
2761 }
2762 return false;
2763}
2764
2765bool StyledTextIOReader_RTF::HandleControlWord_tx (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2766{
2767 if (not controlWord.fHasArg) {
2768 HandleBadlyFormattedInput (); // must have a numeric color-number argument
2769 }
2770 else {
2771 StandardTabStopList* curTabs = &readerContext.GetCurrentGroupContext ()->fDestinationContext.fTabStops;
2772 CoordinateType lastStop = 0;
2773 for (auto i = curTabs->fTabStops.begin (); i != curTabs->fTabStops.end (); ++i) {
2774 lastStop += *i;
2775 }
2776 TWIPS newStop = TWIPS (controlWord.fValue);
2777 if (newStop <= lastStop) {
2778 HandleBadlyFormattedInput ();
2779 return false; // Allow this to be a recoverable error by ignoring it... LGP 2000-09-20
2780 }
2781 Assert (newStop > lastStop);
2782 curTabs->fTabStops.push_back (TWIPS (newStop - lastStop));
2783 readerContext.GetDestination ().SetTabStops (*curTabs);
2784 }
2785 return false;
2786}
2787
2788bool StyledTextIOReader_RTF::HandleControlWord_u ([[maybe_unused]] ReaderContext& readerContext, [[maybe_unused]] const RTFIO::ControlWord& controlWord)
2789{
2790 // Unclear how I should treat this for the NON-UNICODE Led case. I COULD read the UNICODE chars - and then map them to narrow. But probably just
2791 // as good to just read whatever narrow characters were already there.
2792 // LGP 2000/04/29
2793 if (controlWord.fHasArg) {
2794 readerContext.fSkipNextNChars_UC = readerContext.fUnicodeUCValue;
2795 wchar_t u = static_cast<wchar_t> (controlWord.fValue);
2796 readerContext.GetDestination ().AppendText (&u, 1);
2797 }
2798 else {
2799 HandleBadlyFormattedInput ();
2800 }
2801 return false;
2802}
2803
2804bool StyledTextIOReader_RTF::HandleControlWord_uc ([[maybe_unused]] ReaderContext& readerContext, [[maybe_unused]] const RTFIO::ControlWord& controlWord)
2805{
2806 if (not controlWord.fHasArg) {
2807 readerContext.fUnicodeUCValue = 1;
2808 }
2809 else {
2810 readerContext.fUnicodeUCValue = max (0L, controlWord.fValue);
2811 }
2812 return false;
2813}
2814
2815bool StyledTextIOReader_RTF::HandleControlWord_ul (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2816{
2817 ApplyFontSpec (readerContext, controlWord);
2818 return false;
2819}
2820
2821bool StyledTextIOReader_RTF::HandleControlWord_ulnone (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2822{
2823 ApplyFontSpec (readerContext, controlWord);
2824 return false;
2825}
2826
2827bool StyledTextIOReader_RTF::HandleControlWord_up (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2828{
2829 /*
2830 * Kludge/workaround for SPR#1620.
2831 */
2832 if (controlWord.fHasArg and controlWord.fValue > 0) {
2833 RTFIO::ControlWord newCW;
2834 newCW.fHasArg = false;
2835 newCW.fWord = RTFIO::eControlAtom_super;
2836 return HandleControlWord_super (readerContext, newCW);
2837 }
2838 return false;
2839}
2840
2841bool StyledTextIOReader_RTF::HandleControlWord_v (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2842{
2843 CheckIfAboutToStartBody (readerContext);
2844 if (readerContext.GetCurrentGroupContext () == nullptr) {
2845 HandleBadlyFormattedInput (true); // cannot set font name without a current group!
2846 }
2847 bool startHidden = controlWord.fHasArg ? controlWord.fValue : true;
2848 readerContext.GetDestination ().SetTextHidden (readerContext.GetCurrentGroupContext ()->fDestinationContext.fTextHidden = startHidden);
2849 return false;
2850}
2851
2852bool StyledTextIOReader_RTF::HandleControlWord_UnknownControlWord (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2853{
2854 (void)HandlePossibleSpecialCharacterControlWord (readerContext, controlWord);
2855 return false;
2856}
2857
2858bool StyledTextIOReader_RTF::HandlePossibleSpecialCharacterControlWord (ReaderContext& readerContext, const RTFIO::ControlWord& controlWord)
2859{
2860 // Lookup. If good, then insert special character, and return true. Else return false to handle normally.
2861 for (size_t i = 0; i < Memory::NEltsOf (kMappings); ++i) {
2862 if (controlWord.fWord == kMappings[i].fControlWordName) {
2863 CheckIfAboutToStartBody (readerContext);
2864 readerContext.GetDestination ().AppendText (&kMappings[i].fUNICODECharacter, 1);
2865 return true;
2866 }
2867 }
2868 return false;
2869}
2870
2871void StyledTextIOReader_RTF::ReadCommentGroup (ReaderContext& readerContext)
2872{
2873 /*
2874 * Comment groups are often used for newly introduced groups/features, so they can
2875 * be ignored by old readers, and still convey info to NEW readers. So we should
2876 * by default - just inore them. But this routine is virutal, and its easy for
2877 * subclassers to hook this routine, read the next control word, and see if it is
2878 * one they know about, and else fall through to inherrited (THIS) version, to
2879 * discard the rest of the group.
2880 */
2881 ConsumeNextChar (); // Called looking at the '*' characcter
2882 if (PeekNextChar () == RTFIO::kRTFStartTagChar) {
2883 RTFIO::ControlWord cword = ReadControlWord ();
2884 if (cword.fWord == RTFIO::eControlAtom_pn) {
2885 ReadIn_pn_Group (readerContext);
2886 return;
2887 }
2888 }
2889
2890 SkipToEndOfCurrentGroup (); // we ignore unknown groups
2891}
2892
2893void StyledTextIOReader_RTF::ReadIn_pn_Group (ReaderContext& readerContext)
2894{
2895 /*
2896 * Exceedingly primitive support for reading in 'Word 6.0 and Word 95 RTF' style
2897 * bullet and paragraph info data. See examples from SPR#1550, and the RTF 1.7 docs
2898 * under the heading "Bullets and Numbering / Word 6.0 and Word 95 RTF" for more
2899 * info on how to improve this reading code.
2900 */
2901 ListStyle listStyle = eListStyle_Bullet;
2902 if (readerContext.GetParentGroupContext () != nullptr) {
2903 readerContext.GetDestination ().SetListStyle (readerContext.GetParentGroupContext ()->fDestinationContext.fListStyle = listStyle);
2904 }
2905 SkipToEndOfCurrentGroup ();
2906}
2907
2908#define qTryQuickISXXX 1
2909inline bool quickIsAlpha (char c)
2910{
2911 return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z');
2912}
2913inline bool quickIsDigit (char c)
2914{
2915 return (c >= '0' and c <= '9');
2916}
2917
2918RTFIO::ControlWord StyledTextIOReader_RTF::ReadControlWord ()
2919{
2920 /*
2921 * WARNING: THIS ROUTINE ASSUMES 'a'..'z' and '0'..'9' are contiguous.
2922 * This is true for ASCII.
2923 */
2924 RTFIO::ControlWord controlWord;
2925
2926 if (GetNextChar () != RTFIO::kRTFStartTagChar) {
2927 Require (false); // should only be called when we have one...
2928 HandleBadlyFormattedInput (true);
2929 Assert (false); // NOTREACHED
2930 }
2931
2932 /*
2933 * All control-words consist of lower-case alpha characters (see RTF Syntax section of
2934 * MS rtf spec), then followed by an optional number. Note funny space char delimiter
2935 * behavior is part of spec.
2936 */
2937 char c = GetNextChar ();
2938
2939 /*
2940 * See spr#0619.
2941 *
2942 * Can an RTF control word be broken up over multiple lines? Who knows!
2943 *
2944 * The RTF 1.5 spec says pretty clearly what a control word is:
2945 *
2946 * \LetterSequence<Delimiter>
2947 * Note that a backslash begins each control word.
2948 * The LetterSequence is made up of lowercase alphabetic characters between
2949 * "a" and "z" inclusive. RTF is case sensitive, and all RTF control words must be lowercase.
2950 *
2951 * That means that you CANNOT have a newline after the \ and still have a valid control-word? Right?
2952 *
2953 * But - I've been sent sample RTF files that DO have a CRLF after the leading \ (in the \colortbl).
2954 * And the RTF 1.5 spec itself - in its first example - shows this RTF text:
2955 *
2956 * {\rtf\ansi\deff0{\fonttbl{\f0\froman Tms Rmn;}{\f1\fdecor
2957 * Symbol;}{\f2\fswiss Helv;}}{\colortbl;\red0\green0\blue0;
2958 * \red0\green0\blue255;\red0\green255\blue255;\red0\green255\
2959 * blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\
2960 * green255\blue0;\red255\green255\blue255;}{\stylesheet{\fs20 \snext0Normal;}}{\info{\author John Doe}
2961 * {\creatim\yr1990\mo7\dy30\hr10\min48}{\version1}{\edmins0}
2962 * {\nofpages1}{\nofwords0}{\nofchars0}{\vern8351}}\widoctrl\ftnbj \sectd\linex0\endnhere \pard\plain \fs20 This is plain text.\par}
2963 *
2964 *
2965 * Also note that the RTF 1.5 spec goes on to say:
2966 *
2967 * A carriage return (character value 13) or linefeed (character value 10) will be
2968 * treated as a \par control if the character is preceded by a backslash. You must
2969 * include the backslash; otherwise, RTF ignores the control word.
2970 *
2971 * Pretty clearly - the MSWord RTF readers ignore this dictate - at least under some circumstances.
2972 *
2973 * To ignore this in MY code - you would have to sprinkle
2974 * #if 0
2975 * while (c == '\n' or c == '\r') {
2976 * c = GetNextChar (); // Skip CR/LFs - see above See spr#0619 comment.
2977 * }
2978 * #endif
2979 * in several places below.
2980 *
2981 * But I thought that too risky. Way too likely to cause OTHER bugs. So instead - I've simply made the code
2982 * less brittle when it finds errors in input RTF. It simply fails to parse (fully) a given colortable entry, and then
2983 * moves on.
2984 */
2985
2986 {
2987 RTFIO::ControlWordAtomName controlWordBuf;
2988 size_t controlWordLen = 0;
2989 for (; quickIsAlpha (c); c = GetNextChar ()) { // NB: used to check (c >= 'a' and c <= 'z') as the RTF 1.5 spec calls for.
2990 // It clearly says these controlwords must be lower case. However - their sample
2991 // reader code merely checks for isalpha(). And - I've found MSWord2000 seems to sometimes
2992 // generate undocumented control words that have mixed case. Sigh. Guess they hadn't read
2993 // the spec :-).--- SPR#0764
2994
2995 // LGP 2003-03-17 slight speed tweek - having max buf size and throwing away controlword chars more than max buf size
2996 // (really should NEVER happen as controlwords shouldn't ever get that long).
2997 if (controlWordLen < RTFIO::eMaxControlAtomNameLen) {
2998 controlWordBuf[controlWordLen] = c;
2999 ++controlWordLen;
3000 }
3001#if qStroika_Foundation_Debug_AssertionsChecked
3002 else {
3003 Assert (false); // just a notification that I should probably up the eMaxControlAtomNameLen define...
3004 }
3005#endif
3006 }
3007 controlWordBuf[controlWordLen] = '\0';
3008 controlWord.fWord = RTFIO::EnterControlWord (controlWordBuf);
3009 }
3010 // note at this point, 'c' contains our DELIMITER.
3011
3012 // According to our RTF Spec, if the 'c' is a SPACE, it is part of the control word (and eaten)
3013 if (c == ' ') {
3014 // EAT ME
3015 }
3016 // or it is a NUMBER, and we must read/consume the #
3017 else if (c == '-' or quickIsDigit (c)) {
3018 bool negative = bool (c == '-');
3019 if (negative) {
3020 c = GetNextChar (); // so we start loop looking at right character
3021 }
3022 unsigned long number = 0; // shift N add to read/build #
3023 for (; quickIsDigit (c); c = GetNextChar ()) {
3024 number *= 10ul;
3025 number += (unsigned long)(c - '0');
3026 }
3027 controlWord.fHasArg = true;
3028 // Note that this can OVERFLOW/UNDERFLOW for very large numbers? Maybe?
3029 // Most of spec only provides for 2-byte ints anyhow, so that should rarely, if ever, be a problem.
3030 controlWord.fValue = negative ? -long (number) : number;
3031 if (c == ' ') {
3032 // EAT ME
3033 }
3034 // or it is something ELSE, and we must put it BACK
3035 else {
3036 PutBackLastChar ();
3037 }
3038 }
3039 // or it is something ELSE, and we must put it BACK
3040 else {
3041 PutBackLastChar ();
3042 }
3043
3044 return controlWord;
3045}
3046
3047void StyledTextIOReader_RTF::AboutToStartBody (ReaderContext& readerContext)
3048{
3049 Require (not readerContext.fStartedBodyYet);
3050 readerContext.fStartedBodyYet = true;
3051
3052 if (readerContext.fDefaultFontNumber != size_t (-1)) {
3053 // Set the initial font of our destination to this font spec.
3054 RequireNotNull (readerContext.GetCurrentGroupContext ());
3055 IncrementalFontSpecification fontSpec = readerContext.GetCurrentGroupContext ()->fDestinationContext.fFontSpec;
3056
3057 // We probably SHOULD do a Led_ThrowBadFormatDataException () here, but on pastes from MS Word 5.1, this sometimes happens
3058 // with no font-table. Go figure!
3059 if (readerContext.fFontTable != nullptr) {
3060 IncrementalFontSpecification a = readerContext.fFontTable->GetFontSpec (readerContext.fDefaultFontNumber);
3061 if (a.GetFontNameSpecifier_Valid ()) {
3062 fontSpec.SetFontNameSpecifier (a.GetFontNameSpecifier ());
3063
3064 FontSpecification newPlain = GetPlainFont ();
3065 newPlain.SetFontNameSpecifier (a.GetFontNameSpecifier ());
3066 SetPlainFont (newPlain);
3067 }
3068 }
3069 readerContext.GetDestination ().UseFont (readerContext.GetCurrentGroupContext ()->fDestinationContext.fFontSpec = fontSpec);
3070 }
3071}
3072
3073FontTableEntry StyledTextIOReader_RTF::ReadInFontTablesEntry ()
3074{
3075 /*
3076 * We must be looking at the open brace. Read in the entire group (consuming the close brace),
3077 * And return this summary info entry record.
3078 */
3079 FontTableEntry entry;
3080
3081 if (GetNextChar () != RTFIO::kRTFOpenGroupChar) {
3082 Require (false); // should only be called when looking at one
3083 //Led_ThrowBadFormatDataException ();
3084 }
3085ReadRest:
3086 while (PeekNextChar () == RTFIO::kRTFStartTagChar) {
3087 RTFIO::ControlWord cword = ReadControlWord ();
3088 switch (cword.fWord) {
3089 case RTFIO::eControlAtom_f: {
3090 if (not cword.fHasArg) {
3091 HandleBadlyFormattedInput (); // font number declarations need an argument (the font #)
3092 }
3093 else {
3094 entry.fFNum = cword.fValue;
3095 }
3096 } break;
3097
3098 case RTFIO::eControlAtom_fnil:
3099 entry.fFamily = FontTableEntry::eNil;
3100 break;
3101 case RTFIO::eControlAtom_froman:
3102 entry.fFamily = FontTableEntry::eRoman;
3103 break;
3104 case RTFIO::eControlAtom_fswiss:
3105 entry.fFamily = FontTableEntry::eSwiss;
3106 break;
3107 case RTFIO::eControlAtom_fmodern:
3108 entry.fFamily = FontTableEntry::eModern;
3109 break;
3110 case RTFIO::eControlAtom_fscript:
3111 entry.fFamily = FontTableEntry::eScript;
3112 break;
3113 case RTFIO::eControlAtom_fdecor:
3114 entry.fFamily = FontTableEntry::eDecor;
3115 break;
3116 case RTFIO::eControlAtom_ftech:
3117 entry.fFamily = FontTableEntry::eTech;
3118 break;
3119 case RTFIO::eControlAtom_fbidi:
3120 entry.fFamily = FontTableEntry::eBidi;
3121 break;
3122
3123 case RTFIO::eControlAtom_fcharset: {
3124 if (not cword.fHasArg) {
3125 HandleBadlyFormattedInput ();
3126 }
3127 else {
3128 entry.fCharSet = static_cast<uint8_t> (cword.fValue);
3129 }
3130 } break;
3131 case RTFIO::eControlAtom_fprq: {
3132 if (not cword.fHasArg) {
3133 HandleBadlyFormattedInput ();
3134 }
3135 else {
3136 entry.fPitch = cword.fValue;
3137 }
3138 } break;
3139 }
3140 }
3141
3142 // spr#506 - As of Word97 - it sometimes sticks comment groups (eg {\*\panose})
3143 // in middle of fonttable entry. Ignore any subgroups in a font-table-entry, as we don't
3144 // know how to handle them.
3145 if (PeekNextChar () == RTFIO::kRTFOpenGroupChar) {
3146 ConsumeNextChar ();
3147 SkipToEndOfCurrentGroup ();
3148 goto ReadRest;
3149 }
3150
3151 size_t startOfName = GetSrcStream ().current_offset ();
3152 ScanForwardFor ("{;");
3153 entry.fFontName = String::FromNarrowSDKString (GrabString (startOfName)).AsSDKString ();
3154
3155 // SEE SPR#0749 - can also get these funky groups after the font name - skip them til I better understand them...
3156 if (PeekNextChar () == RTFIO::kRTFOpenGroupChar) {
3157 ConsumeNextChar ();
3158 SkipToEndOfCurrentGroup ();
3159 }
3160
3161 ScanForwardFor ("}");
3162 if (GetNextChar () != RTFIO::kRTFCloseGroupChar) {
3163 HandleBadlyFormattedInput (true); // EOF reading font-entry
3164 Assert (false); // NOTREACHED
3165 }
3166
3167 return entry;
3168}
3169
3170void StyledTextIOReader_RTF::ReadInObjectSubGroupEntry (ReaderContext& readerContext, vector<char>* data, size_t* resultFoundAt)
3171{
3172 RequireNotNull (data);
3173 if (GetNextChar () != RTFIO::kRTFOpenGroupChar) {
3174 HandleBadlyFormattedInput (true);
3175 Assert (false); // NOTREACHED
3176 }
3177 if (PeekNextChar () == RTFIO::kRTFStartTagChar) {
3178
3179 {
3180 // See if the group is really a \result group - and if so - mark it - and skip out - not an \objdata group!
3181 size_t xxxxxxx = readerContext.GetReader ().GetSrcStream ().current_offset ();
3182 RTFIO::ControlWord xxx = ReadControlWord ();
3183 if (xxx.fWord == RTFIO::eControlAtom_result) {
3184 *resultFoundAt = readerContext.GetReader ().GetSrcStream ().current_offset ();
3185 SkipToEndOfCurrentGroup ();
3186 return;
3187 }
3188 else {
3189 GetSrcStream ().seek_to (xxxxxxx);
3190 }
3191 }
3192
3193 // See if we are looking at an \*\objdata, and if so, consume the data and store it away.
3194 // otherwise, ignore this group.
3195 ConsumeNextChar ();
3196 if (PeekNextChar () == '*') {
3197 ConsumeNextChar ();
3198 }
3199 if (PeekNextChar () == RTFIO::kRTFStartTagChar) {
3200 RTFIO::ControlWord cword = ReadControlWord ();
3201 if (cword.fWord == RTFIO::eControlAtom_objdata) {
3202 ReadObjData (data);
3203 return;
3204 }
3205 }
3206 }
3207 // if it wasn't an objdata and we somehow didnt read anything, simply skip to the end of the group.
3208 SkipToEndOfCurrentGroup ();
3209}
3210
3211void StyledTextIOReader_RTF::ReadObjData (vector<char>* data)
3212{
3213 RequireNotNull (data);
3214
3215 // Keep reading hex characters, and concatenate them onto 'data'. When we get a close-brace, we are done.
3216 for (char c = PeekNextChar (); c != RTFIO::kRTFCloseGroupChar; c = PeekNextChar ()) {
3217 if (c == '\n' or c == '\r') {
3218 ConsumeNextChar (); // Skip CR/LFs
3219 continue;
3220 }
3221 data->push_back (GetNextRTFHexByte ());
3222 }
3223 Assert (PeekNextChar () == RTFIO::kRTFCloseGroupChar);
3224 ConsumeNextChar (); // Eat terminating brace
3225}
3226
3227void StyledTextIOReader_RTF::ConstructOLEEmebddingFromRTFInfo ([[maybe_unused]] ReaderContext& readerContext, [[maybe_unused]] TWIPS_Point size,
3228 [[maybe_unused]] size_t nBytes, [[maybe_unused]] const void* data)
3229{
3230#if qStroika_Foundation_Common_Platform_Windows
3231 using RTFOLEEmbedding = RTFIO::RTFOLEEmbedding;
3232 //const Led_ClipFormat kOLEEmbedClipFormat = static_cast<Led_ClipFormat> (::RegisterClipboardFormat (_T ("Object Descriptor")));
3233 const vector<EmbeddedObjectCreatorRegistry::Assoc>& types = EmbeddedObjectCreatorRegistry::Get ().GetAssocList ();
3234 for (size_t i = 0; i < types.size (); ++i) {
3235 EmbeddedObjectCreatorRegistry::Assoc assoc = types[i];
3236 if (memcmp (assoc.fEmbeddingTag, RTFIO::RTFOLEEmbedding::kEmbeddingTag, sizeof (RTFIO::RTFOLEEmbedding::kEmbeddingTag)) == 0) {
3237 AssertNotNull (assoc.fReadFromMemory);
3238 SimpleEmbeddedObjectStyleMarker* embedding = (assoc.fReadFromMemory) (RTFIO::RTFOLEEmbedding::kEmbeddingTag, data, nBytes);
3239 RTFOLEEmbedding* rtfe = dynamic_cast<RTFOLEEmbedding*> (embedding);
3240 if (rtfe != nullptr) {
3241 rtfe->PostCreateSpecifyExtraInfo (size);
3242 }
3243 try {
3244 readerContext.GetDestination ().AppendEmbedding (embedding);
3245 return;
3246 }
3247 catch (...) {
3248 delete embedding;
3249 throw;
3250 }
3251 }
3252 }
3253#endif
3254 Execution::Throw (DataExchange::BadFormatException::kThe); // Will be caught by caller, and use "unknown embedding object"
3255}
3256
3257void StyledTextIOReader_RTF::ConstructLedEmebddingFromRTFInfo (ReaderContext& readerContext, size_t nBytes, const void* data)
3258{
3259#if qStroika_Frameworks_Led_SupportGDI
3260 // The first sizeof (Led_PrivateEmbeddingTag) bytes are the type tag, and the rest is standard
3261 // internalize/externalize data.
3262 if (nBytes < sizeof (Led_PrivateEmbeddingTag)) {
3263 Execution::Throw (DataExchange::BadFormatException::kThe); // Will be caught by caller, and use "unknown embedding object"
3264 }
3265 const char* tag = (const char*)data;
3266 const char* theData = tag + sizeof (Led_PrivateEmbeddingTag);
3267 size_t theDataNBytes = nBytes - sizeof (Led_PrivateEmbeddingTag);
3268 const vector<EmbeddedObjectCreatorRegistry::Assoc>& types = EmbeddedObjectCreatorRegistry::Get ().GetAssocList ();
3269 for (size_t i = 0; i < types.size (); ++i) {
3270 EmbeddedObjectCreatorRegistry::Assoc assoc = types[i];
3271 if (memcmp (assoc.fEmbeddingTag, tag, sizeof (assoc.fEmbeddingTag)) == 0) {
3272 AssertNotNull (assoc.fReadFromMemory);
3273 SimpleEmbeddedObjectStyleMarker* embedding = (assoc.fReadFromMemory) (tag, theData, theDataNBytes);
3274 try {
3275 readerContext.GetDestination ().AppendEmbedding (embedding);
3276 return;
3277 }
3278 catch (...) {
3279 delete embedding;
3280 throw;
3281 }
3282 }
3283 }
3284#endif
3285 Execution::Throw (DataExchange::BadFormatException::kThe); // Will be caught by caller, and use "unknown embedding object"
3286}
3287
3288void StyledTextIOReader_RTF::ReadPictData (vector<char>* data)
3289{
3290 RequireNotNull (data);
3291
3292 // Keep reading hex characters, and concatenate them onto 'data'. When we get a close-brace or start of another tag, we are done.
3293 // This routine can be called many times to append more and more to 'data'.
3294 for (char c = PeekNextChar (); c != RTFIO::kRTFCloseGroupChar and c != RTFIO::kRTFStartTagChar; c = PeekNextChar ()) {
3295 if (c == '\n' or c == '\r') {
3296 ConsumeNextChar (); // Skip CR/LFs
3297 continue;
3298 }
3299 PUSH_BACK (*data, GetNextRTFHexByte ());
3300 }
3301 Assert (PeekNextChar () == RTFIO::kRTFCloseGroupChar or PeekNextChar () == RTFIO::kRTFStartTagChar);
3302}
3303
3304/*
3305@METHOD: StyledTextIOReader_RTF::ReadTopLevelPictData
3306@DESCRIPTION: <p>Read in the data from a \pict group.</p>
3307*/
3308void StyledTextIOReader_RTF::ReadTopLevelPictData (TWIPS_Point* shownSize, ImageFormat* imageFormat, TWIPS_Point* bmSize, vector<char>* objData)
3309{
3310#if qStroika_Frameworks_Led_SupportGDI
3311 using UnknownRTFEmbedding = RTFIO::UnknownRTFEmbedding;
3312#endif
3313 using ControlWord = RTFIO::ControlWord;
3314
3315 *imageFormat = eDefaultImageFormat;
3316 *shownSize = TWIPS_Point (TWIPS{0}, TWIPS{0});
3317 *bmSize = TWIPS_Point (TWIPS{0}, TWIPS{0});
3318 *objData = vector<char> ();
3319 float scaleX = 1.0f;
3320 float scaleY = 1.0f;
3321
3322 while (true) {
3323 switch (PeekNextChar ()) {
3324 case RTFIO::kRTFStartTagChar: {
3325 ControlWord cw = ReadControlWord ();
3326 switch (cw.fWord) {
3327 case RTFIO::eControlAtom_emfblip:
3328 *imageFormat = eEMF;
3329 break;
3330 case RTFIO::eControlAtom_pngblip:
3331 *imageFormat = ePNG;
3332 break;
3333 case RTFIO::eControlAtom_jpegblip:
3334 *imageFormat = eJPEG;
3335 break;
3336 case RTFIO::eControlAtom_macpict:
3337 *imageFormat = eMacPICT;
3338 break;
3339 case RTFIO::eControlAtom_pmmetafile:
3340 *imageFormat = ePMMetaFile;
3341 break;
3342 case RTFIO::eControlAtom_wmetafile:
3343 *imageFormat = eWMF;
3344 break;
3345 case RTFIO::eControlAtom_dibitmap:
3346 *imageFormat = eDIB;
3347 break;
3348 case RTFIO::eControlAtom_wbitmap:
3349 *imageFormat = eBITMAP;
3350 break;
3351 case RTFIO::eControlAtom_picw:
3352 if (not cw.fHasArg) {
3353 HandleBadlyFormattedInput ();
3354 break;
3355 };
3356 bmSize->h = TWIPS (cw.fValue);
3357 break;
3358 case RTFIO::eControlAtom_pich:
3359 if (not cw.fHasArg) {
3360 HandleBadlyFormattedInput ();
3361 break;
3362 };
3363 bmSize->v = TWIPS (cw.fValue);
3364 break;
3365 case RTFIO::eControlAtom_picwgoal:
3366 if (not cw.fHasArg) {
3367 HandleBadlyFormattedInput ();
3368 break;
3369 };
3370 shownSize->h = TWIPS (cw.fValue);
3371 break;
3372 case RTFIO::eControlAtom_pichgoal:
3373 if (not cw.fHasArg) {
3374 HandleBadlyFormattedInput ();
3375 break;
3376 };
3377 shownSize->v = TWIPS (cw.fValue);
3378 break;
3379 case RTFIO::eControlAtom_picscalex:
3380 if (not cw.fHasArg) {
3381 HandleBadlyFormattedInput ();
3382 break;
3383 };
3384 scaleX = cw.fValue / 100.0f;
3385 break;
3386 case RTFIO::eControlAtom_picscaley:
3387 if (not cw.fHasArg) {
3388 HandleBadlyFormattedInput ();
3389 break;
3390 };
3391 scaleY = cw.fValue / 100.0f;
3392 break;
3393 }
3394 } break;
3395
3396 case RTFIO::kRTFOpenGroupChar: {
3397 // Right now - we don't understand any subgroups - so skip them.
3398 ConsumeNextChar ();
3399 SkipToEndOfCurrentGroup ();
3400 } break;
3401
3402 case RTFIO::kRTFCloseGroupChar: {
3403 /*
3404 * Sanity check shown size.
3405 */
3406 if (shownSize->h == 0) {
3407 shownSize->h = bmSize->h;
3408 }
3409 if (shownSize->v == 0) {
3410 shownSize->v = bmSize->v;
3411 }
3412
3413 shownSize->h *= scaleX;
3414 shownSize->v *= scaleY;
3415#if qStroika_Frameworks_Led_SupportGDI
3416 if (shownSize->v > 20000 or shownSize->h > 20000 or shownSize->h < 10 or shownSize->v < 10) {
3417 *shownSize = UnknownRTFEmbedding::CalcStaticDefaultShownSize ();
3418 }
3419#endif
3420
3421 ConsumeNextChar ();
3422 return; // ALL DONE
3423 } break;
3424 default: {
3425 ReadPictData (objData);
3426 } break;
3427 }
3428 }
3429}
3430
3431/*
3432@METHOD: StyledTextIOReader_RTF::ConstructDIBFromData
3433@DESCRIPTION: <p>Take the given size and data parameters, and consturct a new Led_DIB (which must be freed by caller using delete()). Returns nullptr
3434 if unable to convert the given format.</p>
3435*/
3436Led_DIB* StyledTextIOReader_RTF::ConstructDIBFromData ([[maybe_unused]] TWIPS_Point shownSize, ImageFormat imageFormat,
3437 [[maybe_unused]] TWIPS_Point bmSize, size_t nBytes, const void* data)
3438{
3439 if (data == nullptr) {
3440 HandleBadlyFormattedInput ();
3441 return nullptr; // allow bad input to be treated as unknown format - just return nullptr...
3442 }
3443 switch (imageFormat) {
3444 case eDIB: {
3445 const Led_DIB* dib = reinterpret_cast<const Led_DIB*> (data);
3446 if (Led_GetDIBImageByteCount (dib) != nBytes) {
3447 HandleBadlyFormattedInput ();
3448 return nullptr; // allow bad input to be treated as unknown format - just return nullptr...
3449 }
3450 return Led_CloneDIB (dib);
3451 } break;
3452#if qStroika_Foundation_Common_Platform_Windows
3453 case eEMF: {
3454 Led_DIB* result = nullptr;
3455 HENHMETAFILE hMF = ::SetEnhMetaFileBits (static_cast<UINT> (nBytes), reinterpret_cast<const unsigned char*> (data));
3456 try {
3457 result = ConstructDIBFromEMFHelper (shownSize, bmSize, hMF);
3458 }
3459 catch (...) {
3460 Verify (::DeleteEnhMetaFile (hMF));
3461 throw;
3462 }
3463 Verify (::DeleteEnhMetaFile (hMF));
3464 return result;
3465 } break;
3466 case eWMF: {
3467 Led_DIB* result = nullptr;
3468 HENHMETAFILE hMF = ::SetWinMetaFileBits (static_cast<UINT> (nBytes), reinterpret_cast<const unsigned char*> (data), nullptr, nullptr);
3469 try {
3470 result = ConstructDIBFromEMFHelper (shownSize, bmSize, hMF);
3471 }
3472 catch (...) {
3473 Verify (::DeleteEnhMetaFile (hMF));
3474 throw;
3475 }
3476 Verify (::DeleteEnhMetaFile (hMF));
3477 return result;
3478 } break;
3479#endif
3480 }
3481 return nullptr;
3482}
3483
3484#if qStroika_Foundation_Common_Platform_Windows
3485/*
3486@METHOD: StyledTextIOReader_RTF::ConstructDIBFromEMFHelper
3487@DESCRIPTION: <p>Construct a Led_DIB given HENHMETAFILE, the desired 'shownSize' and 'bmSize' can OVERRIDE the
3488 size specified in the metafile itself.</p>
3489 <p>This routine is only available if @'qStroika_Foundation_Common_Platform_Windows'.</p>
3490*/
3491Led_DIB* StyledTextIOReader_RTF::ConstructDIBFromEMFHelper (TWIPS_Point shownSize, [[maybe_unused]] TWIPS_Point bmSize, const HENHMETAFILE hMF)
3492{
3493 RequireNotNull (hMF);
3494
3495 ENHMETAHEADER header{};
3496 Verify (::GetEnhMetaFileHeader (hMF, sizeof (header), &header) == sizeof (header));
3497
3498 // Don't know best way to get a DIB from a metafile - but this way I HOPE will at least WORK!
3499 Tablet screenDC = (::GetWindowDC (nullptr)); // not sure what DC to use to convert MetaFile to DIB - but this seems like a decent guess
3500
3501 DistanceType hSize = screenDC.CvtFromTWIPSH (shownSize.h);
3502 DistanceType vSize = screenDC.CvtFromTWIPSV (shownSize.v);
3503 Led_Rect imageRect = Led_Rect (0, 0, vSize, hSize);
3504
3505 Tablet memDC;
3506 Bitmap memoryBitmap;
3507
3508 {
3509 ThrowIfZeroGetLastError (memDC.CreateCompatibleDC (&screenDC));
3510 ThrowIfZeroGetLastError (memoryBitmap.CreateCompatibleBitmap (screenDC, hSize, vSize));
3511 HBITMAP oldBitmapInDC = memDC.SelectObject (memoryBitmap);
3512
3513 // Erase the background of the image
3514 {
3515 Led_Rect eraser = imageRect;
3516 Color eraseColor = Led_GetTextBackgroundColor ();
3517 Brush backgroundBrush (eraseColor.GetOSRep ());
3518 GDI_Obj_Selector pen (&memDC, ::GetStockObject (NULL_PEN));
3519 GDI_Obj_Selector brush (&memDC, backgroundBrush);
3520 ++eraser.right; // lovely - windows doesn't count last pixel... See Docs for Rectangle() and rephrase!!!
3521 ++eraser.bottom;
3522 memDC.Rectangle (AsRECT (eraser));
3523 }
3524
3525 // Create a Palette to be used in the offscreen bitmap - if there was one in the MetaFile
3526 HPALETTE usePalette = nullptr;
3527 {
3528 UINT nPalEntries = ::GetEnhMetaFilePaletteEntries (hMF, 0, nullptr);
3529 Assert (nPalEntries != GDI_ERROR);
3530 if (nPalEntries != 0) {
3531 LOGPALETTE* paletteData = reinterpret_cast<LOGPALETTE*> (new char[sizeof (LOGPALETTE) + nPalEntries * sizeof (PALETTEENTRY)]);
3532 paletteData->palVersion = 0;
3533 paletteData->palNumEntries = static_cast<WORD> (nPalEntries);
3534 Verify (::GetEnhMetaFilePaletteEntries (hMF, nPalEntries, paletteData->palPalEntry) == nPalEntries);
3535 usePalette = ::CreatePalette (paletteData);
3536 delete[] (char*)paletteData;
3537 }
3538 }
3539 HPALETTE oldPalette = nullptr;
3540 if (usePalette != nullptr) {
3541 oldPalette = ::SelectPalette (memDC, usePalette, true);
3542 ::RealizePalette (memDC);
3543 }
3544
3545 // Play the acual MetaFile into the offscreen bitmap
3546 bool failed = false;
3547 {
3548 RECT rect = AsRECT (imageRect);
3549 if (PlayEnhMetaFile (memDC.m_hDC, hMF, &rect) == 0) {
3550 failed = true;
3551 }
3552 }
3553
3554 if (oldPalette != nullptr) {
3555 ::SelectPalette (memDC, oldPalette, true);
3556 }
3557 if (usePalette != nullptr) {
3558 ::DeleteObject (usePalette);
3559 }
3560 if (oldBitmapInDC != nullptr) {
3561 (void)memDC.SelectObject (oldBitmapInDC);
3562 }
3563 if (failed) {
3564 HandleBadlyFormattedInput ();
3565 return nullptr; // allow bad input to be treated as unknown format - just return nullptr...
3566 }
3567 }
3568
3569 return Led_DIBFromHBITMAP (memDC.m_hDC, memoryBitmap);
3570}
3571#endif
3572
3573void StyledTextIOReader_RTF::ApplyFontSpec (ReaderContext& readerContext, const RTFIO::ControlWord& cw)
3574{
3575 CheckIfAboutToStartBody (readerContext);
3576 if (readerContext.GetCurrentGroupContext () == nullptr) {
3577 HandleBadlyFormattedInput (true); // cannot set font name without a current group!
3578 }
3579 IncrementalFontSpecification fontSpec = readerContext.GetCurrentGroupContext ()->fDestinationContext.fFontSpec;
3580
3581 switch (cw.fWord) {
3582 case RTFIO::eControlAtom_plain: {
3583 fontSpec = GetPlainFont ();
3584 // after looking at sample RTF docs (like RTF 1.5 spec) - it seems PLAIN should be interpreted as resetting the hidden flag...
3585 readerContext.GetDestination ().SetTextHidden (readerContext.GetCurrentGroupContext ()->fDestinationContext.fTextHidden = false);
3586 } break;
3587
3588 case RTFIO::eControlAtom_cf: {
3589 if (not cw.fHasArg) {
3590 HandleBadlyFormattedInput (); // must have a numeric color-number argument
3591 return; // just ignore the cf record...
3592 }
3593 fontSpec.SetTextColor (LookupColor (readerContext, static_cast<size_t> (cw.fValue)));
3594 } break;
3595
3596 case RTFIO::eControlAtom_f: {
3597 if (readerContext.fFontTable == nullptr) {
3598 // We probably SHOULD do a Led_ThrowBadFormatDataException () here, but on pastes from MS Word 5.1, this sometimes happens
3599 // with no font-table. Go figure!
3600 return;
3601 }
3602 if (not cw.fHasArg) {
3603 HandleBadlyFormattedInput (true); // must have a numeric font-number argument
3604 }
3605 IncrementalFontSpecification a = readerContext.fFontTable->GetFontSpec (cw.fValue);
3606 if (a.GetFontNameSpecifier_Valid ()) {
3607 fontSpec.SetFontNameSpecifier (a.GetFontNameSpecifier ());
3608 }
3609
3610 AssertNotNull (readerContext.fFontTable);
3611 const FontTableEntry* fte = readerContext.fFontTable->LookupEntryByNumber (cw.fValue);
3612 if (fte == nullptr) {
3613 // We probably SHOULD do a Led_ThrowBadFormatDataException () here,
3614 // see spr#0696
3615 Assert (false); // was getting this cuz of bad fonttable generation with hidden text - Just test if we still get - but not really a bug to get here - since input RTF could really be bad!
3616 // LGP 2000/04/26
3617 return;
3618 }
3619#if qStroika_Foundation_Common_Platform_Windows
3620 if (fte->fCharSet != -1) {
3621 // Not sure what I should do if Win32CharSetToCodePage returns zero? -- LGP 2002-12-08
3622 CodePage cp = Platform::Windows::Win32CharSetToCodePage (fte->fCharSet);
3623 if (cp != 0) {
3624 readerContext.GetCurrentGroupContext ()->fCurrentCodePage = cp;
3625 }
3626 readerContext.UseInputCharSetEncoding (readerContext.GetCurrentGroupContext ()->fCurrentCodePage);
3627 }
3628#endif
3629 } break;
3630
3631 case RTFIO::eControlAtom_fs: {
3632 if (not cw.fHasArg) {
3633 HandleBadlyFormattedInput (true); // must have a numeric font-size argument
3634 }
3635 int newSize = cw.fValue / 2;
3636 if (newSize < 4) {
3637 newSize = 4;
3638 }
3639 if (newSize > 128) {
3640 newSize = 128;
3641 }
3642#if qStroika_Foundation_Common_Platform_Windows
3643 if (newSize == fCachedFontSize) {
3644 fontSpec.PokeAtTMHeight (fCachedFontSizeTMHeight);
3645 break;
3646 }
3647#endif
3648 fontSpec.SetPointSize (static_cast<FontSpecification::FontSize> (newSize)); //pinned above 4..128
3649#if qStroika_Foundation_Common_Platform_Windows
3650 fCachedFontSize = static_cast<FontSpecification::FontSize> (newSize);
3651 fCachedFontSizeTMHeight = fontSpec.PeekAtTMHeight ();
3652#endif
3653 } break;
3654
3655 case RTFIO::eControlAtom_b: {
3656 bool turnStyleOn = true; // no arg means ON
3657 if (cw.fHasArg) {
3658 turnStyleOn = cw.fValue;
3659 }
3660 fontSpec.SetStyle_Bold (turnStyleOn);
3661 } break;
3662
3663 case RTFIO::eControlAtom_i: {
3664 bool turnStyleOn = true; // no arg means ON
3665 if (cw.fHasArg) {
3666 turnStyleOn = cw.fValue;
3667 }
3668 fontSpec.SetStyle_Italic (turnStyleOn);
3669 } break;
3670
3671 case RTFIO::eControlAtom_sub: {
3672 fontSpec.SetStyle_SubOrSuperScript (FontSpecification::eSubscript);
3673 } break;
3674 case RTFIO::eControlAtom_super: {
3675 fontSpec.SetStyle_SubOrSuperScript (FontSpecification::eSuperscript);
3676 } break;
3677 case RTFIO::eControlAtom_strike: {
3678#if qStroika_Foundation_Common_Platform_Windows
3679 bool turnStyleOn = true; // no arg means ON
3680 if (cw.fHasArg) {
3681 turnStyleOn = cw.fValue;
3682 }
3683 fontSpec.SetStyle_Strikeout (turnStyleOn);
3684#endif
3685 } break;
3686 case RTFIO::eControlAtom_ul: {
3687 bool turnStyleOn = true; // no arg means ON
3688 if (cw.fHasArg) {
3689 turnStyleOn = cw.fValue;
3690 }
3691 fontSpec.SetStyle_Underline (turnStyleOn);
3692 } break;
3693
3694 case RTFIO::eControlAtom_ulnone: {
3695 fontSpec.SetStyle_Underline (false);
3696 } break;
3697 }
3698 readerContext.GetDestination ().UseFont (readerContext.GetCurrentGroupContext ()->fDestinationContext.fFontSpec = fontSpec);
3699}
3700
3701unsigned char StyledTextIOReader_RTF::GetNextRTFHexByte () const
3702{
3703 char c = GetNextChar ();
3704
3705 // First nibble
3706 while (c == '\n' or c == '\r') {
3707 c = GetNextChar (); // Skip CR/LFs
3708 }
3709 unsigned char value = '\0';
3710 if (c >= '0' and c <= '9') {
3711 value = c - '0';
3712 }
3713 else if (c >= 'a' and c <= 'f') {
3714 value = c - 'a' + 10;
3715 }
3716 else if (c >= 'A' and c <= 'F') {
3717 value = c - 'A' + 10;
3718 }
3719 else {
3720 // MAYBE we could consider making this RECOVERABLE - but I've never seen this before, and it seems likely to be a syptom of a badly corrupted file... LGP 2000-09-20
3721 HandleBadlyFormattedInput (true);
3722 }
3723
3724 // Second nibble
3725 c = GetNextChar ();
3726 while (c == '\n' or c == '\r') {
3727 c = GetNextChar (); // Skip CR/LFs
3728 }
3729 unsigned char value2 = '\0';
3730 if (c >= '0' and c <= '9') {
3731 value2 = c - '0';
3732 }
3733 else if (c >= 'a' and c <= 'f') {
3734 value2 = c - 'a' + 10;
3735 }
3736 else if (c >= 'A' and c <= 'F') {
3737 value2 = c - 'A' + 10;
3738 }
3739 else {
3740 // SPR#1489
3741 HandleBadlyFormattedInput (true);
3742 }
3743 value <<= 4;
3744 value += value2;
3745 return value;
3746}
3747
3748string StyledTextIOReader_RTF::ReadInGroupAndSave ()
3749{
3750 size_t startOfGroup = GetSrcStream ().current_offset ();
3751 if (GetNextChar () != RTFIO::kRTFOpenGroupChar) {
3752 HandleBadlyFormattedInput (true);
3753 }
3754 SkipToEndOfCurrentGroup ();
3755 return GrabString (startOfGroup);
3756}
3757
3758void StyledTextIOReader_RTF::SkipToEndOfCurrentGroup ()
3759{
3760 int depth = 1; // assume we start INSIDE a group
3761 for (; depth > 0;) {
3762 ScanForwardFor ("{}");
3763 switch (PeekNextChar ()) {
3764 case RTFIO::kRTFOpenGroupChar: {
3765 ConsumeNextChar ();
3766 ++depth;
3767 } break;
3768 case RTFIO::kRTFCloseGroupChar: {
3769 ConsumeNextChar ();
3770 --depth;
3771 } break;
3772 default: {
3773 Assert (false);
3774 } break;
3775 }
3776 }
3777}
3778
3779void StyledTextIOReader_RTF::ScanForwardFor (const char* setOfChars)
3780{
3781 RequireNotNull (setOfChars);
3782
3783#if qUseCompiledSetHack
3784 bitset<256> compiledSet;
3785 for (auto p = setOfChars; *p != '\0'; ++p) {
3786 compiledSet[(unsigned char)*p] = true;
3787 }
3788#endif
3789
3790 try {
3791 for (char c = GetNextChar ();; c = GetNextChar ()) {
3792 if (c == RTFIO::kRTFQuoteNextCharChar) { // avoid getting confused by embedded '{' etc characters in the actual text.
3793 continue;
3794 }
3795#if qUseCompiledSetHack
3796 if (compiledSet[(unsigned char)c]) {
3797 PutBackLastChar ();
3798 return;
3799 }
3800#else
3801 for (const char* p = setOfChars; *p != '\0'; ++p) {
3802 if (c == *p) {
3803 PutBackLastChar ();
3804 return;
3805 }
3806 }
3807#endif
3808 }
3809 }
3810 catch (ReadEOFException&) {
3811 // ignore these exceptions - just return if we reach EOF
3812 return;
3813 }
3814}
3815
3816/*
3817@METHOD: StyledTextIOReader_RTF::SearchForwardFor
3818@DESCRIPTION: <p><b>Internal/Private</b></p>
3819 <p>Search for the given string (up to a max of maxCharsToExamine characters). Return true iff the string is found
3820 and leave the stream positioned where found. If fail to find - leave stream pointer unchanged. Don't THROW cuz of
3821 end of file - but just treat that as not finding. Other exceptions also return stream to original position.</p>
3822*/
3823bool StyledTextIOReader_RTF::SearchForwardFor (const char* searchFor, size_t maxCharsToExamine)
3824{
3825 RequireNotNull (searchFor);
3826 SrcStream& srcStream = GetSrcStream ();
3827 size_t origOffset = srcStream.current_offset ();
3828 size_t matchStrLen = ::strlen (searchFor);
3829 Require (matchStrLen >= 1);
3830 try {
3831 RetryNextMatch:
3832 char c = '\0';
3833 for (; (c = GetNextChar ()) != '\0';) {
3834 if (c == *searchFor) {
3835 // Compare further...
3836 size_t foundAt = srcStream.current_offset () - 1;
3837 for (c = GetNextChar (); matchStrLen > (srcStream.current_offset () - foundAt); c = GetNextChar ()) {
3838 size_t idx = srcStream.current_offset () - foundAt - 1;
3839 if (searchFor[idx] == c) {
3840 continue; // keep matching
3841 }
3842 else {
3843 // bad match...
3844 srcStream.seek_to (foundAt + 1);
3845 goto RetryNextMatch;
3846 }
3847 }
3848 // If we fall through - must have been a good match!
3849 Assert (matchStrLen == srcStream.current_offset () - foundAt);
3850 srcStream.seek_to (foundAt);
3851 return true;
3852 }
3853 if (srcStream.current_offset () > origOffset + maxCharsToExamine) {
3854 srcStream.seek_to (origOffset);
3855 return false;
3856 }
3857 }
3858 }
3859 catch (ReadEOFException) {
3860 srcStream.seek_to (origOffset);
3861 return false;
3862 }
3863 catch (...) {
3864 srcStream.seek_to (origOffset);
3865 throw;
3866 }
3867 return false;
3868}
3869
3870RTFInfo& StyledTextIOReader_RTF::GetRTFInfo () const
3871{
3872 AssertNotNull (fRTFInfo);
3873 return *fRTFInfo;
3874}
3875
3876/*
3877 ********************************************************************************
3878 ************************ StyledTextIOWriter_RTF::WriterContext *****************
3879 ********************************************************************************
3880 */
3881size_t StyledTextIOWriter_RTF::WriterContext::GetCurSrcOffset () const
3882{
3883 return GetSrcStream ().current_offset ();
3884}
3885
3886#if qStroika_Frameworks_Led_SupportGDI
3887SimpleEmbeddedObjectStyleMarker* StyledTextIOWriter_RTF::WriterContext::GetCurSimpleEmbeddedObjectStyleMarker () const
3888{
3889 size_t offset = GetCurSrcOffset ();
3890 vector<SimpleEmbeddedObjectStyleMarker*> embeddingsList = GetSrcStream ().CollectAllEmbeddingMarkersInRange (offset - 1, offset);
3891 Assert (embeddingsList.size () <= 1); // cuz we gave a range of one, and can only have a single
3892 // embedding in one place. Allow for there to be NONE - if the user
3893 // wants to allow having NUL characters in his text for other reasons.
3894 if (embeddingsList.empty ()) {
3895 return nullptr;
3896 }
3897 else {
3898 return embeddingsList[0];
3899 }
3900}
3901#endif
3902
3903StyledTextIOWriter_RTF::Table* StyledTextIOWriter_RTF::WriterContext::GetCurRTFTable () const
3904{
3905 return GetSrcStream ().GetTableAt (GetCurSrcOffset () - 1);
3906}
3907
3908/*
3909 ********************************************************************************
3910 ***************************** StyledTextIOWriter_RTF ***************************
3911 ********************************************************************************
3912 */
3913StyledTextIOWriter_RTF::StyledTextIOWriter_RTF (SrcStream* srcStream, SinkStream* sinkStream, RTFInfo* rtfInfo)
3914 : StyledTextIOWriter (srcStream, sinkStream)
3915 , fCurrentOutputCharSetEncoding{WellKnownCodePages::kANSI}
3916 , fRTFInfo{rtfInfo}
3917 , fDocumentCharacterSet{WellKnownCodePages::kANSI}
3918 , fSoftLineBreakChar (srcStream->GetSoftLineBreakCharacter ())
3919 , fHidableTextRuns (srcStream->GetHidableTextRuns ())
3920{
3921 static const pair<string, wchar_t> kCharsWrittenByName[] = {
3922 pair<string, wchar_t> ("tab", L'\x0009'), pair<string, wchar_t> ("emdash", L'\x2013'),
3923 pair<string, wchar_t> ("endash", L'\x2014'), pair<string, wchar_t> ("lquote", L'\x2018'),
3924 pair<string, wchar_t> ("rquote", L'\x2019'), pair<string, wchar_t> ("ldblquote", L'\x201c'),
3925 pair<string, wchar_t> ("rdblquote", L'\x201d'),
3926 };
3927 SetCharactersSavedByName (vector<pair<string, wchar_t>> (&kCharsWrittenByName[0], &kCharsWrittenByName[Memory::NEltsOf (kCharsWrittenByName)]));
3928}
3929
3930StyledTextIOWriter_RTF::~StyledTextIOWriter_RTF ()
3931{
3932 delete fFontTable;
3933 delete fColorTable;
3934 delete fListTable;
3935}
3936
3937void StyledTextIOWriter_RTF::UseOutputCharSetEncoding (CodePage codePage)
3938{
3939 fCurrentOutputCharSetEncoding = codePage;
3940}
3941
3942void StyledTextIOWriter_RTF::SetCharactersSavedByName (const vector<pair<string, wchar_t>>& charactersSavedByName)
3943{
3944 fCharactersSavedByName = charactersSavedByName;
3945 for (vector<pair<string, wchar_t>>::const_iterator i = fCharactersSavedByName.begin (); i != fCharactersSavedByName.end (); ++i) {
3946 fCharactersSavedByName_Name2Char.insert (map<string, wchar_t>::value_type (i->first, i->second));
3947 fCharactersSavedByName_Char2Name.insert (map<wchar_t, string>::value_type (i->second, i->first));
3948 }
3949}
3950
3951void StyledTextIOWriter_RTF::Write ()
3952{
3953 WriterContext writerContext (*this);
3954
3955 write (RTFIO::kRTFOpenGroupChar);
3956 WriteHeader (writerContext);
3957 WriteBody (writerContext);
3958 write (RTFIO::kRTFCloseGroupChar);
3959}
3960
3961void StyledTextIOWriter_RTF::WriteHeader (WriterContext& writerContext)
3962{
3963 WriteTag ("rtf1");
3964 WriteDocCharset ();
3965 WriteFontTable (writerContext);
3966 WriteColorTable (writerContext);
3967 WriteListTable ();
3968 if (fRTFInfo != nullptr) {
3969 WriteTagNValue ("deftab", fRTFInfo->fDefaultTabStop);
3970 }
3971 WriteGenerator ();
3972}
3973
3974void StyledTextIOWriter_RTF::WriteBody (WriterContext& writerContext)
3975{
3976 /*
3977 * Walk through the characters, and output them one at a time. Walk
3978 * SIMULTANEOUSLY through the style run information, and output new controlling
3979 * tags on the fly.
3980 */
3981 AssureStyleRunSummaryBuilt (writerContext);
3982
3983 WriteStartParagraph (writerContext);
3984 writerContext.fLastEmittedISR = StyledInfoSummaryRecord (IncrementalFontSpecification (), 0);
3985 writerContext.fNextStyleChangeAt = 0;
3986 writerContext.fIthStyleRun = 0;
3987 if (not fHidableTextRuns.empty ()) {
3988 writerContext.fNextHidableTextChangeAt = fHidableTextRuns[0].fOffsetFromPrev;
3989 }
3990 Led_tChar c = '\0';
3991 while (writerContext.GetSrcStream ().readNTChars (&c, 1) != 0) {
3992 WriteBodyCharacter (writerContext, c);
3993 }
3994 if (writerContext.fHidableTextRegionOpen) {
3995 write ("}");
3996 writerContext.fNextHidableTextChangeAt = size_t (-1);
3997 }
3998}
3999
4000void StyledTextIOWriter_RTF::WriteBodyCharacter (WriterContext& writerContext, Led_tChar c)
4001{
4002 /*
4003 * Note - WriteBodyCharacter () gets called AFTER we've read the next character, so the GetCurSrcOffset () is pointing one past the
4004 * character we are processing. Thats why here we say "writerContext.GetCurSrcOffset ()-1"
4005 */
4006
4007 // HidableText state changes...
4008 if (writerContext.GetCurSrcOffset () - 1 == writerContext.fNextHidableTextChangeAt) {
4009 if (writerContext.fHidableTextRegionOpen) {
4010 write ("}");
4011 ++writerContext.fIthHidableTextRun;
4012 if (writerContext.fIthHidableTextRun < fHidableTextRuns.size ()) {
4013 writerContext.fNextHidableTextChangeAt += fHidableTextRuns[writerContext.fIthHidableTextRun].fOffsetFromPrev;
4014 }
4015 else {
4016 writerContext.fNextHidableTextChangeAt = size_t (-1);
4017 }
4018 }
4019 else {
4020 write ("{");
4021 WriteTag ("v");
4022 writerContext.fNextHidableTextChangeAt += fHidableTextRuns[writerContext.fIthHidableTextRun].fElementLength;
4023 }
4024 writerContext.fHidableTextRegionOpen = not writerContext.fHidableTextRegionOpen;
4025 }
4026
4027 // Style changes
4028 if (writerContext.GetCurSrcOffset () - 1 == writerContext.fNextStyleChangeAt) {
4029 const StyledInfoSummaryRecord& nextStyleRun = fStyleRunSummary[writerContext.fIthStyleRun];
4030 if (writerContext.GetCurSrcOffset () <= 1) { // 1+ cuz we've read one character
4031 EmitBodyFontInfoChange (writerContext, nextStyleRun);
4032 }
4033 else {
4034 EmitBodyFontInfoChange (writerContext, nextStyleRun, writerContext.fLastEmittedISR);
4035 }
4036 writerContext.fLastEmittedISR = nextStyleRun;
4037 writerContext.fNextStyleChangeAt += writerContext.fLastEmittedISR.fLength;
4038 ++writerContext.fIthStyleRun;
4039 }
4040
4041 if (writerContext.fCharsToSkip > 0) {
4042 --writerContext.fCharsToSkip;
4043 return;
4044 }
4045
4046 switch (c) {
4047 case '\n': {
4048 WriteTag ("par");
4049 WriteStartParagraph (writerContext);
4050 } break;
4051
4052 case '{': {
4053 write ("\\{");
4054 } break;
4055
4056 case '}': {
4057 write ("\\}");
4058 } break;
4059
4060 case '\\': {
4061 write ("\\\\");
4062 } break;
4063
4064#if qStroika_Frameworks_Led_SupportGDI
4065 case kEmbeddingSentinelChar: {
4066 unique_ptr<StyledTextIOWriter_RTF::Table> table (writerContext.GetCurRTFTable ());
4067 if (table.get () != nullptr) {
4068 Assert (writerContext.fCharsToSkip == 0); // must preserve / restore for nested tables?
4069 WriteTable (writerContext, table.get ());
4070 size_t x = table->GetOffsetEnd ();
4071 Assert (x >= 1);
4072 writerContext.fCharsToSkip = x - 1;
4073 break;
4074 }
4075
4076 SimpleEmbeddedObjectStyleMarker* embedding = writerContext.GetCurSimpleEmbeddedObjectStyleMarker ();
4077 if (embedding == nullptr) {
4078 WriteHexCharHelper (kEmbeddingSentinelChar);
4079 }
4080 else {
4081 if (PossiblyWriteUnknownRTFEmbedding (writerContext, embedding)) {
4082 break;
4083 }
4084 else if (PossiblyWriteOLERTFEmbedding (writerContext, embedding)) {
4085 break;
4086 }
4087 else if (PossiblyWritePICTEmbedding (writerContext, embedding)) {
4088 break;
4089 }
4090 WritePrivatLedEmbedding (writerContext, embedding);
4091 }
4092 } break;
4093#endif
4094
4095 default: {
4096 if (c == fSoftLineBreakChar) {
4097 WriteTag ("line");
4098 break;
4099 }
4100 map<wchar_t, string>::const_iterator i = fCharactersSavedByName_Char2Name.find (c);
4101 wchar_t uc = c;
4102 if (i == fCharactersSavedByName_Char2Name.end ()) {
4103 WritePlainUnicodeCharCharacterHelper (uc);
4104 }
4105 else {
4106 WriteTag ((*i).second.c_str ());
4107 }
4108 } break;
4109 }
4110
4111 if ((writerContext.GetCurSrcOffset ()) % 80 == 0) {
4112 write ("\r\n"); // should write a newline every once in a while...
4113 // according to RTF spec
4114 }
4115}
4116
4117void StyledTextIOWriter_RTF::WritePlainUnicodeCharCharacterHelper (wchar_t c)
4118{
4119 char mbCharBuf[2];
4120 size_t mbCharCount = 2;
4121 // NOTE - this code was written with assumption of char16_t == wchar_t - but newer Stroika more picky, so if ever happens just drop
4122 // on floor for now --LGP 2023-07-27
4123 mbCharCount = CodeCvt<char16_t>{fCurrentOutputCharSetEncoding}
4124 .Characters2Bytes (Memory::SpanBytesCast<span<char16_t>> (span{&c, 1}), Memory::SpanBytesCast<span<byte>> (span{mbCharBuf}))
4125 .size ();
4126 Assert (mbCharCount == 1 or mbCharCount == 2);
4127
4128 bool needToWriteUNICODE = c >= 0x80; // write UNICODE if non-ascii
4129 if (needToWriteUNICODE) {
4130 // write \uc\u stuff
4131 WriteTagNValue ("uc", static_cast<int> (mbCharCount)); // keep track of prev value of this guy - so we don't need to write so often - in a context object???
4132 WriteTagNValue ("u", c);
4133 }
4134
4135 unsigned char uc = mbCharBuf[0];
4136 if (uc < 0x80) {
4137 write (uc);
4138 }
4139 else {
4140 WriteHexCharHelper (uc);
4141 }
4142 if (mbCharCount == 2) {
4143 uc = mbCharBuf[1];
4144 if (uc < 0x80) {
4145 write (uc);
4146 }
4147 else {
4148 WriteHexCharHelper (uc);
4149 }
4150 }
4151}
4152
4153void StyledTextIOWriter_RTF::WriteHexCharHelper (unsigned char c)
4154{
4155 // write hex char
4156 char buf[5];
4157 buf[0] = '\\';
4158 buf[1] = '\'';
4159 buf[2] = ConvertWriteSingleHexDigit_ (c / 16);
4160 buf[3] = ConvertWriteSingleHexDigit_ (c % 16);
4161 buf[4] = '\0';
4162 write (buf);
4163}
4164
4165void StyledTextIOWriter_RTF::WriteStartParagraph (WriterContext& writerContext)
4166{
4167 WriteTag ("pard");
4168 if (writerContext.fInTable) {
4169 WriteTag ("intbl");
4170 }
4171 switch (writerContext.GetSrcStream ().GetJustification ()) {
4172 case eCenterJustify:
4173 WriteTag ("qc");
4174 break;
4175 case eRightJustify:
4176 WriteTag ("qr");
4177 break;
4178 case eFullyJustify:
4179 WriteTag ("qj");
4180 break;
4181 default:
4182 break; // ignore .... \\pard will capture this...
4183 }
4184 StandardTabStopList tabStops = writerContext.GetSrcStream ().GetStandardTabStopList ();
4185 // assume we only save the specified tabstops, and that the default is already saved per-doc
4186 // since RTF1.4 doesn't seem to have a per-para 'deftabstop' value
4187 TWIPS tabSoFar = TWIPS{0};
4188 for (size_t i = 0; i < tabStops.fTabStops.size (); ++i) {
4189 tabSoFar += tabStops.fTabStops[i];
4190 WriteTagNValue ("tx", tabSoFar);
4191 }
4192
4193 {
4194 TWIPS fi = writerContext.GetSrcStream ().GetFirstIndent ();
4195 if (fi != 0) { // don't bother writing if default value
4196 WriteTagNValue ("fi", fi);
4197 }
4198 }
4199
4200 {
4201 TWIPS lhs = TWIPS{0};
4202 TWIPS rhs = TWIPS{0};
4203 writerContext.GetSrcStream ().GetMargins (&lhs, &rhs);
4204 if (lhs != 0) { // don't bother writing if default value
4205 WriteTagNValue ("li", lhs);
4206 }
4207
4208 TWIPS effectiveDrawingWidth = fRTFInfo == nullptr ? RTFInfo ().GetEffectiveDrawingWidth () : fRTFInfo->GetEffectiveDrawingWidth ();
4209 TWIPS rhsRTFMarginInTWIPS = TWIPS (effectiveDrawingWidth - rhs);
4210 if (rhsRTFMarginInTWIPS != 0) { // don't bother writing if default value
4211 WriteTagNValue ("ri", rhsRTFMarginInTWIPS);
4212 }
4213 }
4214
4215 {
4216 TWIPS sb = writerContext.GetSrcStream ().GetSpaceBefore ();
4217 if (sb != 0) {
4218 WriteTagNValue ("sb", sb);
4219 }
4220 }
4221 {
4222 TWIPS sa = writerContext.GetSrcStream ().GetSpaceAfter ();
4223 if (sa != 0) {
4224 WriteTagNValue ("sa", sa);
4225 }
4226 }
4227 {
4228 LineSpacing sl = writerContext.GetSrcStream ().GetLineSpacing ();
4229 if (sl.fRule != LineSpacing::eSingleSpace) {
4230 CoordinateType rtfsl = 1000;
4231 bool multi = true;
4232 mkRTFValues_From_LineSpacing (sl, &rtfsl, &multi);
4233 if (rtfsl != 1000) {
4234 WriteTagNValue ("sl", rtfsl);
4235 WriteTagNValue ("slmult", multi);
4236 }
4237 }
4238 }
4239 {
4240 ListStyle listStyle = eListStyle_None;
4241 unsigned char indentLevel = 0;
4242 writerContext.GetSrcStream ().GetListStyleInfo (&listStyle, &indentLevel);
4243 if (listStyle != eListStyle_None) {
4244 /// quickie - wrong list level and style - but tmp hack... SPR#0955
4245 {
4246 write ("{");
4247 WriteTag ("listtext");
4248 write ("\\'b7");
4249 WriteTag ("tab");
4250 write ("}");
4251 }
4252
4253 //tmphack - REALLY must grab from REAL listtable records!!!
4254 int listTableID = 1;
4255 WriteTagNValue ("ls", listTableID);
4256 if (indentLevel != 0) {
4257 WriteTagNValue ("ilvl", indentLevel);
4258 }
4259 }
4260 }
4261}
4262
4263void StyledTextIOWriter_RTF::WriteTable (WriterContext& writerContext, Table* table)
4264{
4265 using CellInfo = StyledTextIOWriter_RTF::Table::CellInfo;
4266 RequireNotNull (table);
4267 write ("\r\n"); // should write a newline every once in a while...
4268 // according to RTF spec
4269 size_t rows = table->GetRows ();
4270 for (size_t r = 0; r < rows; ++r) {
4271 WriteTag ("trowd");
4272
4273 TWIPS_Rect cellMargins = table->GetDefaultCellMarginsForRow (r);
4274 TWIPS_Rect cellSpacing = table->GetDefaultCellSpacingForRow (r);
4275 {
4276 WriteTagNValue ("trgaph", (cellMargins.top + cellMargins.left + cellMargins.bottom + cellMargins.right) / 4);
4277 }
4278 {
4279 if (cellSpacing.top != 0) {
4280 WriteTagNValue ("trspdt", cellSpacing.top / 2);
4281 WriteTagNValue ("trspdft", 3);
4282 }
4283 if (cellSpacing.left != 0) {
4284 WriteTagNValue ("trspdl", cellSpacing.left / 2);
4285 WriteTagNValue ("trspdfl", 3);
4286 }
4287 if (cellSpacing.bottom != 0) {
4288 WriteTagNValue ("trspdb", cellSpacing.bottom / 2);
4289 WriteTagNValue ("trspdfb", 3);
4290 }
4291 if (cellSpacing.right != 0) {
4292 WriteTagNValue ("trspdr", cellSpacing.right / 2);
4293 WriteTagNValue ("trspdfr", 3);
4294 }
4295 }
4296
4297 {
4298 if (cellMargins.top != 0) {
4299 WriteTagNValue ("trpaddt", cellMargins.top);
4300 WriteTagNValue ("trpaddft", 3);
4301 }
4302 if (cellMargins.left != 0) {
4303 WriteTagNValue ("trpaddl", cellMargins.left);
4304 WriteTagNValue ("trpaddfl", 3);
4305 }
4306 if (cellMargins.bottom != 0) {
4307 WriteTagNValue ("trpaddb", cellMargins.bottom);
4308 WriteTagNValue ("trpaddfb", 3);
4309 }
4310 if (cellMargins.right != 0) {
4311 WriteTagNValue ("trpaddr", cellMargins.right);
4312 WriteTagNValue ("trpaddfr", 3);
4313 }
4314 }
4315
4316 vector<CellInfo> cellInfos;
4317 table->GetRowInfo (r, &cellInfos);
4318 size_t columns = cellInfos.size ();
4319 //<celldef>s
4320 {
4321 TWIPS cellxSoFar = TWIPS{0};
4322 for (vector<CellInfo>::const_iterator i = cellInfos.begin (); i != cellInfos.end (); ++i) {
4323 WriteTagNValue ("clcbpat", static_cast<int> (fColorTable->LookupColor (i->f_clcbpat)));
4324
4325 // This value of 3/2 * LHS is somewhat empirically derived from the output of MS Word XP. Its really quite
4326 // hadly documented - the relationship between cell spacing and cellx values.
4327 // LGP 2003-05-01 - SPR#1396 (now corresponding change in reader)
4328 TWIPS cellWidthIncludingSpacing = (*i).f_cellx + TWIPS (3 * cellSpacing.left / 2);
4329
4330 cellxSoFar += cellWidthIncludingSpacing;
4331 WriteTagNValue ("cellx", cellxSoFar); // NB: cellx marks the END of a <celldef> in the RTF 1.7 spec
4332 }
4333 }
4334// Actual CELLs
4335#if qStroika_Foundation_Debug_AssertionsChecked
4336 size_t nCellsWritten = 0;
4337#endif
4338 for (size_t c = 0; c < columns; ++c) {
4339 unique_ptr<StyledTextIOWriter::SrcStream> srcStream = unique_ptr<StyledTextIOWriter::SrcStream>{table->MakeCellSubSrcStream (r, c)};
4340 if (srcStream.get () != nullptr) {
4341 WriterContext wc{writerContext, *srcStream.get ()};
4342 vector<StyledInfoSummaryRecord> x = fStyleRunSummary;
4343 fStyleRunSummary.clear ();
4344 AssureStyleRunSummaryBuilt (wc);
4345 WriteBody (wc);
4346 fStyleRunSummary = x;
4347 WriteTag ("cell");
4348#if qStroika_Foundation_Debug_AssertionsChecked
4349 ++nCellsWritten;
4350#endif
4351 }
4352 }
4353#if qStroika_Foundation_Debug_AssertionsChecked
4354 Assert (nCellsWritten == cellInfos.size ()); // must write same number of cells as celldefs
4355#endif
4356 WriteTag ("row");
4357 write ("\r\n"); // should write a newline every once in a while...
4358 // according to RTF spec
4359
4360 // SPR#1406 must treat \row as same as \par - in that both trigger the start of a new paragraph
4361 // (pard generation)
4362 WriteStartParagraph (writerContext);
4363 }
4364}
4365
4366#if qStroika_Frameworks_Led_SupportGDI
4367bool StyledTextIOWriter_RTF::PossiblyWriteUnknownRTFEmbedding (WriterContext& /*writerContext*/, SimpleEmbeddedObjectStyleMarker* embedding)
4368{
4369 // Now see if it is an RTF embedding, and if so, write it out.
4370 if (RTFIO::UnknownRTFEmbedding* ee = dynamic_cast<RTFIO::UnknownRTFEmbedding*> (embedding)) {
4371 // Since UnknownRTFEmbedding is a simple typedef, it will map to any undefiend object embedding.
4372 // Which is fine, its what we want. But we only want to insert the ones which we created from RTF.
4373 // This is important cuz others probably don't actually contain valid rtf. And important to tkae
4374 // any unknown object which has this type cuz could have gotten here via cut/paste, undo, etc,
4375 // so the object would have lost its dynamic type anyhow (if we counted on an exact
4376 // match to a subtype RTFIO::UnknownRTFEmbedding).
4377 if (memcmp (ee->GetTag (), RTFIO::kRTFBodyGroupFragmentEmbeddingTag, sizeof (RTFIO::kRTFBodyGroupFragmentEmbeddingTag)) == 0) {
4378 write (ee->GetData (), ee->GetDataLength ());
4379 return true;
4380 }
4381 }
4382 return false;
4383}
4384
4385bool StyledTextIOWriter_RTF::PossiblyWriteOLERTFEmbedding (WriterContext& /*writerContext*/, SimpleEmbeddedObjectStyleMarker* embedding)
4386{
4387 // Now see if it is an OLE RTF embedding, and if so, write it out.
4388 if (RTFIO::RTFOLEEmbedding* anRTFOLEEmbedding = dynamic_cast<RTFIO::RTFOLEEmbedding*> (embedding)) {
4389 // Get encoded format of the object in OLE1 OLESTREAM format
4390 size_t nBytes;
4391 byte* theDataBytes = nullptr;
4392 anRTFOLEEmbedding->DoWriteToOLE1Stream (&nBytes, &theDataBytes);
4393
4394 write ("\r\n");
4395 write ("{");
4396 WriteTag ("object");
4397 WriteTag ("objemb");
4398 Led_Size size = anRTFOLEEmbedding->GetSize ();
4399 WriteTagNValue ("objh", Led_CvtScreenPixelsToTWIPSV (size.v));
4400 WriteTagNValue ("objw", Led_CvtScreenPixelsToTWIPSH (size.h));
4401
4402 // WriteTagNValue ("objh", 100); // get real values here!
4403 // WriteTagNValue ("objw", 100); // get real values here!
4404 //&&&FIX&&&objw ETC
4405
4406 string className = String::FromSDKString (anRTFOLEEmbedding->GetObjClassName ()).AsNarrowSDKString ();
4407 if (not className.empty ()) {
4408 write ("{\\*\\objclass ");
4409 // probably SHOULD check className doesn't have any bad characters, like a "{" - or some such...
4410 // But I think Win32 may protect us against such bad names in ProgIDs?
4411 write (className.c_str ());
4412 write ("}");
4413 }
4414
4415 //Write the ObjData block
4416 write ("{");
4417 write ("\\*\\objdata ");
4418 WriteHexCharDataBlock (nBytes, theDataBytes);
4419 delete[] theDataBytes;
4420 write ("}");
4421
4422 // Someday add support for \result - but not TODAY
4423
4424 // DONE
4425 write ("}");
4426
4427 return true;
4428 }
4429 return false;
4430}
4431
4432// WOULD LIKE TO WRITE BOTH dibitmap and wmetafile - but then I can only read dibitmap on MAC (maybe COULD read wmetafile on Mac if I studied wmetafile format better!
4433// Also - for some reason - I cannot get MSWord to read consistently when I write either DIB or WMF. I THINK there is a bug when I write WMF - cuz I don't read it
4434// back myself consistently. BUt then - neither does MSWord! I can dump a picture into MSWord 2000 - and save, and reopen - using no app but MSWord 2000 - and it
4435// loses the image (sometimes only transiently).
4436// Anyhow - Writing as DIB seems like the best option for the time being...
4437#define qWriteAsDIB 1
4438bool StyledTextIOWriter_RTF::PossiblyWritePICTEmbedding (WriterContext& /*writerContext*/, SimpleEmbeddedObjectStyleMarker* embedding)
4439{
4440 // Now see if it is an OLE RTF embedding, and if so, write it out.
4441 if (StandardDIBStyleMarker* aPictEmbedding = dynamic_cast<StandardDIBStyleMarker*> (embedding)) {
4442 write ("\r\n");
4443 write ("{");
4444 WriteTag ("pict");
4445 const Led_DIB* dib = aPictEmbedding->GetDIBData ();
4446 Led_Size size = Led_GetDIBImageSize (dib);
4447#if !qWriteAsDIB
4448 int vEnhSize = size.v * (1400 / 10) * 2.54;
4449 int hEnhSize = size.h * (1400 / 10) * 2.54;
4450 WriteTagNValue ("pich", size.v * 20);
4451 WriteTagNValue ("picw", size.h * 20);
4452#endif
4453 WriteTagNValue ("pichgoal", Led_CvtScreenPixelsToTWIPSV (size.v));
4454 WriteTagNValue ("picwgoal", Led_CvtScreenPixelsToTWIPSH (size.h));
4455
4456#if qWriteAsDIB
4457 WriteTag ("dibitmap");
4458 const void* theDataBytes = dib;
4459 size_t nBytes = Led_GetDIBImageByteCount (dib);
4460
4461 // See SPR#0811 for details
4462 WriteTagNValue ("pich", Led_CvtScreenPixelsToTWIPSV (size.v));
4463 WriteTagNValue ("picw", Led_CvtScreenPixelsToTWIPSH (size.h));
4464 WriteTagNValue ("wbmbitspixel", dib->bmiHeader.biBitCount == 0 ? 24 : dib->bmiHeader.biBitCount);
4465 WriteTagNValue ("wbmplanes", dib->bmiHeader.biPlanes == 0 ? 1 : dib->bmiHeader.biPlanes);
4466 WriteTagNValue ("wbmwidthbytes", static_cast<int> (Led_GetDIBImageRowByteCount (dib)));
4467#else
4468 WriteTagNValue ("wmetafile", 8); // not sure what 8 means - but thats what MSWord 2000 seems to write - LGP 2000-07-08
4469
4470 void* theDataBytes = nullptr;
4471 size_t nBytes = 0;
4472 unique_ptr<BYTE> theDataBytes_;
4473 {
4474 Tablet screenDC = (::GetWindowDC (nullptr)); // not sure what DC to use to convert MetaFile to DIB - but this seems like a decent guess
4475 UINT mapMode = MM_TEXT;
4476 //UINT mapMode = MM_TWIPS;
4477 HENHMETAFILE hMF = nullptr;
4478 {
4479 RECT rect = AsRECT (Led_Rect (0, 0, vEnhSize, hEnhSize));
4480 HDC hMFDC = ::CreateEnhMetaFile (nullptr, nullptr, &rect, nullptr);
4481 ::SetMapMode (hMFDC, mapMode);
4482 const char* lpBits = reinterpret_cast<const char*> (dib) + Led_GetDIBPalletByteCount (dib) + sizeof (BITMAPINFOHEADER);
4483 Assert (mapMode == MM_TWIPS or mapMode == MM_TEXT);
4484 if (mapMode == MM_TWIPS) {
4485 ::StretchDIBits (hMFDC, 0, 0, Led_CvtScreenPixelsToTWIPSH (size.h), Led_CvtScreenPixelsToTWIPSV (size.v), 0, 0, size.h,
4486 size.v, lpBits, dib, DIB_RGB_COLORS, SRCCOPY);
4487 }
4488 else if (mapMode == MM_TEXT) {
4489 ::StretchDIBits (hMFDC, 0, 0, size.h, size.v, 0, 0, size.h, size.v, lpBits, dib, DIB_RGB_COLORS, SRCCOPY);
4490 }
4491 hMF = ::CloseEnhMetaFile (hMFDC);
4492 }
4493 nBytes = ::GetWinMetaFileBits (hMF, 0, nullptr, mapMode, screenDC);
4494 if (nBytes == 0) {
4495 Assert (false); //??
4496 return false; //??? ERROR
4497 }
4498 BYTE* bytes = new BYTE[nBytes];
4499 Verify (::GetWinMetaFileBits (hMF, nBytes, bytes, mapMode, screenDC) == nBytes);
4500
4501 theDataBytes = bytes;
4502 theDataBytes_ = unique_ptr<BYTE> (bytes);
4503
4504 ::DeleteEnhMetaFile (hMF);
4505 }
4506#endif
4507
4508 //Write the ObjData block
4509 write ("\r\n");
4510 WriteHexCharDataBlock (nBytes, theDataBytes);
4511
4512 // DONE
4513 write ("}");
4514
4515 return true;
4516 }
4517 return false;
4518}
4519#endif
4520
4521#if qGCC_OptBugWithLocalClassesScopedInFunction
4522struct VectorSinkStream : SimpleEmbeddedObjectStyleMarker::SinkStream {
4523public:
4524 virtual void write (const void* buffer, size_t bytes) override
4525 {
4526 using ci = const char*;
4527 fData.insert (fData.end (), ci (buffer), ci (buffer) + bytes);
4528 }
4529 vector<char> fData;
4530};
4531#endif
4532#if qStroika_Frameworks_Led_SupportGDI
4533void StyledTextIOWriter_RTF::WritePrivatLedEmbedding (WriterContext& /*writerContext*/, SimpleEmbeddedObjectStyleMarker* embedding)
4534{
4535#if qBorlandNameInLocalFunctDeclarationSpaceCompilerBug
4536 using namespace Led;
4537#endif
4538#if !qGCC_OptBugWithLocalClassesScopedInFunction
4539 struct VectorSinkStream : SimpleEmbeddedObjectStyleMarker::SinkStream {
4540 public:
4541 virtual void write (const void* buffer, size_t bytes) override
4542 {
4543 using ci = const char*;
4544 fData.insert (fData.end (), ci (buffer), ci (buffer) + bytes);
4545 }
4546 vector<char> fData;
4547 };
4548#endif
4549 VectorSinkStream embeddingData;
4550 embedding->Write (embeddingData);
4551
4552 write ("\r\n");
4553 write ("{");
4554 WriteTag ("object");
4555 WriteTag ("ledprivateobjectembeddingformat");
4556 // WriteTagNValue ("objh", 100); // get real values here!
4557 // WriteTagNValue ("objw", 100); // get real values here!
4558
4559 //Write the ObjData block
4560 write ("{");
4561 write ("\\*\\objdata ");
4562 WriteHexCharDataBlock (sizeof (Led_PrivateEmbeddingTag), embedding->GetTag ());
4563 WriteHexCharDataBlock (embeddingData.fData.size (), &embeddingData.fData.front ());
4564 write ("}");
4565
4566 // Someday add support for \result - but not TODAY
4567
4568 // DONE
4569 write ("}");
4570}
4571#endif
4572
4573void StyledTextIOWriter_RTF::WriteTag (const char* tagStr)
4574{
4575 RequireNotNull (tagStr);
4576 Require (tagStr[0] != '\\'); // we write that ourselves
4577 Require (::strchr (tagStr, ' ') == 0); // we write trailing space, and better note be others!
4578 write ('\\');
4579 write (tagStr);
4580 write (' ');
4581}
4582
4583void StyledTextIOWriter_RTF::WriteTagNValue (const char* tagStr, int value)
4584{
4585 RequireNotNull (tagStr);
4586 Require (tagStr[0] != '\\'); // we write that ourselves
4587 Require (::strchr (tagStr, ' ') == 0); // we write trailing space, and better note be others!
4588 write ('\\');
4589 write (tagStr);
4590 char buf[1024];
4591 (void)snprintf (buf, Memory::NEltsOf (buf), "%d", value);
4592 write (buf);
4593 write (' ');
4594}
4595
4596void StyledTextIOWriter_RTF::WriteHexCharDataBlock (size_t nBytes, const void* resultData)
4597{
4598 const unsigned char* start = (const unsigned char*)resultData;
4599 const unsigned char* end = start + nBytes;
4600 for (const unsigned char* cur = start; cur != end; ++cur) {
4601 WriteRTFHexByte (*cur);
4602 if ((cur - start) % 50 == 0) {
4603 write ("\r\n"); // should write a newline every once in a while...
4604 // according to RTF spec
4605 }
4606 }
4607}
4608
4609void StyledTextIOWriter_RTF::WriteRTFHexByte (unsigned char theByte)
4610{
4611 unsigned char hiNibble = (theByte) >> 4;
4612 unsigned char lowNibble = (theByte) & 0xf;
4613 Require (hiNibble <= 0xf);
4614 Require (lowNibble <= 0xf);
4615 write (ConvertWriteSingleHexDigit_ (hiNibble));
4616 write (ConvertWriteSingleHexDigit_ (lowNibble));
4617}
4618
4619void StyledTextIOWriter_RTF::WriteDocCharset ()
4620{
4621 switch (fDocumentCharacterSet) {
4622 case WellKnownCodePages::kANSI:
4623 WriteTag ("ansi");
4624 break;
4625 case WellKnownCodePages::kMAC:
4626 WriteTag ("mac");
4627 break;
4628 case WellKnownCodePages::kPC:
4629 WriteTag ("pc");
4630 break;
4631 case WellKnownCodePages::kPCA:
4632 WriteTag ("pca");
4633 break;
4634 default: {
4635 // error?
4636 }
4637 }
4638}
4639
4640void StyledTextIOWriter_RTF::WriteFontTable (WriterContext& writerContext)
4641{
4642 write ('{');
4643 WriteTag ("fonttbl");
4644
4645 AssureFontTableBuilt (writerContext);
4646 AssertNotNull (fFontTable);
4647
4648 size_t entryCount = fFontTable->fEntries.size ();
4649 for (size_t i = 0; i < entryCount; ++i) {
4650 WriteFontTablesEntry (fFontTable->fEntries[i]);
4651 }
4652
4653 write ('}');
4654}
4655
4656void StyledTextIOWriter_RTF::WriteFontTablesEntry (const FontTableEntry& entry)
4657{
4658 write ('{');
4659
4660 WriteTagNValue ("f", entry.fFNum);
4661
4662 switch (entry.fFamily) {
4663 case FontTableEntry::eNil:
4664 WriteTag ("fnil");
4665 break;
4666 case FontTableEntry::eRoman:
4667 WriteTag ("froman");
4668 break;
4669 case FontTableEntry::eSwiss:
4670 WriteTag ("fswiss");
4671 break;
4672 case FontTableEntry::eModern:
4673 WriteTag ("fmodern");
4674 break;
4675 case FontTableEntry::eScript:
4676 WriteTag ("fscript");
4677 break;
4678 case FontTableEntry::eDecor:
4679 WriteTag ("fdecor");
4680 break;
4681 case FontTableEntry::eTech:
4682 WriteTag ("ftech");
4683 break;
4684 case FontTableEntry::eBidi:
4685 WriteTag ("fbidi");
4686 break;
4687 }
4688
4689 if (entry.fCharSet != static_cast<uint8_t> (-1)) {
4690 WriteTagNValue ("fcharset", entry.fCharSet);
4691 }
4692
4693 if (entry.fPitch != 0) {
4694 WriteTagNValue ("fprq", entry.fPitch);
4695 }
4696
4697 write (String::FromSDKString (entry.fFontName).AsNarrowSDKString ());
4698
4699 write (';');
4700
4701 write ('}');
4702}
4703
4704void StyledTextIOWriter_RTF::WriteColorTable (WriterContext& writerContext)
4705{
4706 AssureColorTableBuilt (writerContext);
4707 AssertNotNull (fColorTable);
4708
4709 write ('{');
4710 WriteTag ("colortbl");
4711
4712 size_t entryCount = fColorTable->fEntries.size ();
4713 for (size_t i = 0; i < entryCount; ++i) {
4714 Color c = fColorTable->LookupColor (i);
4715 char buf[1024];
4716 (void)snprintf (buf, Memory::NEltsOf (buf), "\\red%d\\green%d\\blue%d;", c.GetRed () >> 8, c.GetGreen () >> 8, c.GetBlue () >> 8);
4717 write (buf);
4718 }
4719
4720 write ('}');
4721}
4722
4723void StyledTextIOWriter_RTF::WriteListTable ()
4724{
4725 //tmphack...
4726 RTFIO::ListTableEntry lte;
4727 lte.fListID = rand ();
4728 lte.fListTemplateID = rand ();
4729 lte.fListStyle = eListStyle_Bullet;
4730 lte.fFontID = 0; // \fN should map to Symbol font????? Find font with bullet char in it....
4731 RTFIO::ListOverrideTableEntry lote;
4732 lote.fListID = lte.fListID;
4733
4734 write ("\r\n{\\*\\listtable");
4735 WriteListTablesEntry (lte);
4736 write ("}\r\n");
4737
4738 write ("{\\*\\listoverridetable");
4739 WriteListOverrideTablesEntry (lote);
4740 write ("}\r\n");
4741}
4742
4743void StyledTextIOWriter_RTF::WriteListTablesEntry (const RTFIO::ListTableEntry& entry)
4744{
4745 write ("{");
4746 WriteTag ("list");
4747 WriteTagNValue ("listtemplateid", entry.fListTemplateID);
4748 write ("{");
4749 WriteTag ("listlevel");
4750 WriteTagNValue ("levelnfc", entry.fListStyle);
4751 WriteTagNValue ("leveljc", 0);
4752 WriteTagNValue ("levelfollow", 0);
4753 WriteTagNValue ("levelstartat", 1);
4754 WriteTagNValue ("levelindent", 0);
4755 write ("{");
4756 int levelTemplateID = rand ();
4757 WriteTag ("leveltext");
4758 WriteTagNValue ("levelnfc", entry.fListStyle);
4759 WriteTagNValue ("leveltemplateid", levelTemplateID);
4760 /*
4761 * Originally cloned/copied MSWord2k output was:
4762 * write ("\\'01\\u-3913 ?;");
4763 *
4764 * Instead now - we write out the 0x2022 bullet character (MSWord assumed Symbol font).
4765 */
4766 write ("\\'01");
4767 WriteTagNValue ("u", 0x2022); // bullet character
4768 write (" ?;");
4769 write ("}");
4770 WriteTagNValue ("f", entry.fFontID);
4771 WriteTagNValue ("fi", -360);
4772 WriteTagNValue ("li", 720);
4773 WriteTag ("jclisttab");
4774 WriteTagNValue ("tx", 720);
4775 write ("}");
4776 WriteTagNValue ("listid", entry.fListID);
4777 write ("}");
4778}
4779
4780void StyledTextIOWriter_RTF::WriteListOverrideTablesEntry (const RTFIO::ListOverrideTableEntry& oEntry)
4781{
4782 write ("{");
4783 WriteTag ("listoverride");
4784 WriteTagNValue ("listid", oEntry.fListID);
4785 WriteTagNValue ("listoverridecount", 0);
4786 WriteTagNValue ("ls", 1);
4787 write ("}");
4788}
4789
4790void StyledTextIOWriter_RTF::WriteGenerator ()
4791{
4792 write ("{\\*");
4793 WriteTag ("generator");
4794 write ("Sophist Solutions, Inc. Led RTF IO Engine - " qLed_ShortVersionString);
4795 write (";");
4796 write ("}");
4797}
4798
4799void StyledTextIOWriter_RTF::EmitBodyFontInfoChange (WriterContext& writerContext, const FontSpecification& newOne)
4800{
4801 RequireNotNull (fFontTable);
4802
4803 WriteTag ("plain");
4804
4805 if (writerContext.fHidableTextRegionOpen) {
4806 WriteTag ("v"); // cuz plain resets the \v flag
4807 }
4808
4809 {
4810 RequireNotNull (fFontTable);
4811 const FontTableEntry* fe = fFontTable->LookupEntryByName (newOne.GetFontName ());
4812 RequireNotNull (fe); // this routine cannot be called with an invalid font table setup. It would imply
4813 // a bug in the AssureFontTableBuilt () code, most probably...
4814 WriteTagNValue ("f", fe->fFNum);
4815 }
4816
4817 // font size
4818 WriteTagNValue ("fs", newOne.GetPointSize () * 2);
4819
4820 // font styles
4821 if (newOne.GetStyle_Bold ()) {
4822 WriteTag ("b");
4823 }
4824 if (newOne.GetStyle_Italic ()) {
4825 WriteTag ("i");
4826 }
4827 if (newOne.GetStyle_Underline ()) {
4828 WriteTag ("ul");
4829 }
4830 switch (newOne.GetStyle_SubOrSuperScript ()) {
4831 case FontSpecification::eSubscript:
4832 WriteTag ("sub");
4833 break;
4834 case FontSpecification::eSuperscript:
4835 WriteTag ("super");
4836 break;
4837 }
4838#if qStroika_Foundation_Common_Platform_Windows
4839 if (newOne.GetStyle_Strikeout ()) {
4840 WriteTag ("strike");
4841 }
4842#endif
4843
4844 WriteTagNValue ("cf", static_cast<int> (fColorTable->LookupColor (newOne.GetTextColor ())));
4845}
4846
4847void StyledTextIOWriter_RTF::EmitBodyFontInfoChange (WriterContext& writerContext, const FontSpecification& newOne, const FontSpecification& /*oldOne*/)
4848{
4849 // lets be simplistic to start with...
4850 EmitBodyFontInfoChange (writerContext, newOne);
4851}
4852
4853void StyledTextIOWriter_RTF::AssureColorTableBuilt (WriterContext& writerContext)
4854{
4855 if (fColorTable == nullptr) {
4856 fColorTable = new RTFIO::ColorTable ();
4857 set<Color> colorsUsed;
4858 writerContext.GetSrcStream ().SummarizeFontAndColorTable (nullptr, &colorsUsed);
4859 for (set<Color>::const_iterator i = colorsUsed.begin (); i != colorsUsed.end (); ++i) {
4860 (void)fColorTable->EnterColor (*i);
4861 }
4862 }
4863}
4864
4865#if qStroika_Foundation_Common_Platform_Windows
4866namespace {
4867 BOOL FAR PASCAL Save_Charset_EnumFontFamiliesProc (ENUMLOGFONTEX* pelf, NEWTEXTMETRICEX* /*lpntm*/, int /*fontType*/, LPVOID pCharset)
4868 {
4869 BYTE* charSet = reinterpret_cast<BYTE*> (pCharset);
4870 *charSet = pelf->elfLogFont.lfCharSet;
4871 return 1;
4872 }
4873}
4874#endif
4875
4876void StyledTextIOWriter_RTF::AssureFontTableBuilt (WriterContext& writerContext)
4877{
4878 if (fFontTable == nullptr) {
4879 fFontTable = new FontTable (); // no need to try/catch cuz its stored in instance var, and will get destroyed
4880 // on StyledTextIOWriter_RTF::DTOR
4881 set<SDKString> fontNames;
4882 writerContext.GetSrcStream ().SummarizeFontAndColorTable (&fontNames, nullptr);
4883#if qStroika_Foundation_Common_Platform_Windows
4884 WindowDC screenDC (nullptr);
4885#endif
4886 for (set<SDKString>::const_iterator i = fontNames.begin (); i != fontNames.end (); ++i) {
4887 const SDKString& name = *i;
4888 if (fFontTable->LookupEntryByName (name) == nullptr) {
4889 FontTableEntry fte;
4890 fte.fFontName = name;
4891//
4892// OLD COMMENTS:
4893// Led 3.0 code had this. SHOULD do something similar - but I'm not sure its worth it. Maybe do differently using
4894// design suggested by some Led customer with my own builtin table of font mapping info.
4895//
4896// Must fill in LOTS more data here - LGP 2000/04/28
4897// In particular - fill in the fCharset field - so we know what charset to use
4898// when writing multibyte text!
4899//
4900// NEW COMMENTS:
4901// Very minimal support to get this working as well as it did for Led 3.0. SPR#1577.
4902// Got basically working enough to fix this bug.
4903//
4904#if qStroika_Foundation_Common_Platform_MacOS
4905 if (name == Led_SDK_TCHAROF ("New York")) {
4906 fte.fFamily = FontTableEntry::eSwiss;
4907 }
4908 else if (name == Led_SDK_TCHAROF ("Geneva")) {
4909 fte.fFamily = FontTableEntry::eRoman;
4910 }
4911 else if (name == Led_SDK_TCHAROF ("Monaco")) {
4912 fte.fFamily = FontTableEntry::eModern;
4913 }
4914 else if (name == Led_SDK_TCHAROF ("Helvetica")) {
4915 fte.fFamily = FontTableEntry::eSwiss;
4916 }
4917 else if (name == Led_SDK_TCHAROF ("Symbol")) {
4918 fte.fFamily = FontTableEntry::eTech;
4919 }
4920 else if (name == Led_SDK_TCHAROF ("Times")) {
4921 fte.fFamily = FontTableEntry::eRoman;
4922 }
4923#elif qStroika_Foundation_Common_Platform_Windows
4924 LOGFONT lf;
4925 (void)::memset (&lf, 0, sizeof (lf));
4926 Characters::CString::Copy (lf.lfFaceName, Memory::NEltsOf (lf.lfFaceName), name.c_str ());
4927 lf.lfCharSet = DEFAULT_CHARSET;
4928 BYTE useCharset = DEFAULT_CHARSET;
4929 ::EnumFontFamiliesEx (screenDC.m_hDC, &lf, (FONTENUMPROC)Save_Charset_EnumFontFamiliesProc, reinterpret_cast<LPARAM> (&useCharset), 0);
4930 if (useCharset != DEFAULT_CHARSET) {
4931 fte.fCharSet = useCharset;
4932 }
4933#endif
4934
4935 fFontTable->Add (fte);
4936 }
4937 }
4938 }
4939}
4940
4941void StyledTextIOWriter_RTF::AssureStyleRunSummaryBuilt (WriterContext& writerContext)
4942{
4943 if (fStyleRunSummary.empty ()) {
4944 size_t totalTextLength = writerContext.GetSrcStream ().GetTotalTextLength ();
4945 if (totalTextLength != 0) {
4946 fStyleRunSummary = writerContext.GetSrcStream ().GetStyleInfo (0, totalTextLength);
4947 }
4948 }
4949}
4950
4951void StyledTextIOWriter_RTF::AssureListTableBuilt (WriterContext& /*writerContext*/)
4952{
4953 if (fListTable == nullptr) {
4954 fListTable = new RTFIO::ListTables ();
4955 }
4956}
#define AssertNotNull(p)
Definition Assertions.h:333
#define RequireNotNull(p)
Definition Assertions.h:347
#define Verify(c)
Definition Assertions.h:419
CodeCvt unifies byte <-> unicode conversions, vaguely inspired by (and wraps) std::codecvt,...
Definition CodeCvt.h:118
nonvirtual size_t Bytes2Characters(span< const byte > from) const
convert span byte (external serialized format) parameters to characters (like std::codecvt<>::in () -...
Definition CodeCvt.inl:750
basic_string< SDKChar > SDKString
Definition SDKString.h:38
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43