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