// // Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved. // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert // McNeel & Associates. // // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF // MERCHANTABILITY ARE HEREBY DISCLAIMED. // // For complete openNURBS copyright information see . // //////////////////////////////////////////////////////////////// #include "opennurbs.h" #if !defined(ON_COMPILING_OPENNURBS) // This check is included in all opennurbs source .c and .cpp files to insure // ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled. // When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined // and the opennurbs .h files alter what is declared and how it is declared. #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif #include "opennurbs_textiterator.h" static bool IsAtoZ(ON__UINT32 ch) { return ( ch < 0xD800 && iswalpha((wchar_t)ch)) ? true : false; } //static bool IsHexDigit(ON__UINT32 ch) //{ // if ( '0' <= ch && ch <= '9') // return true; // if ( 'A' <= ch && ch <= 'F') // return true; // if ( 'a' <= ch && ch <= 'f') // return true; // return false; //} static bool IsDigit(ON__UINT32 ch) { return ( ch < 0xD800 && iswdigit((wchar_t)ch)) ? true : false; } // Rtf tagnames static const wchar_t* tagRtf = L"rtf"; static const wchar_t* tagFontTable = L"fonttbl"; static const wchar_t* tagDefaultFont = L"deff"; static const wchar_t* tagFont = L"f"; static const wchar_t* tagFontSize = L"fs"; static const wchar_t* tagCharSet = L"fcharset"; static const wchar_t* tagCodePage = L"cpg"; static const wchar_t* tagNewline = L"line"; static const wchar_t* tagParagraph = L"par"; static const wchar_t* tagParagraphDefaults = L"pard"; static const wchar_t* tagSection = L"sect"; static const wchar_t* tagTabulator = L"tab"; static const wchar_t* tagBold = L"b"; static const wchar_t* tagItalic = L"i"; static const wchar_t* tagUnderLine = L"ul"; static const wchar_t* tagUnderLineNone = L"ulnone"; static const wchar_t* tagStrikeThrough = L"strike"; static const wchar_t* tagSuperscript = L"super"; static const wchar_t* tagSubscript = L"sub"; static const wchar_t* tagNoSuperSub = L"nosupersub"; //static const wchar_t* tagAlignLeft = L"ql"; //static const wchar_t* tagAlignCenter = L"qc"; //static const wchar_t* tagAlignRight = L"qr"; //static const wchar_t* tagAlignJustify = L"qj"; static const wchar_t* tagColorTable = L"colortbl"; static const wchar_t* tagColorRed = L"red"; static const wchar_t* tagColorGreen = L"green"; static const wchar_t* tagColorBlue = L"blue"; static const wchar_t* tagColorForeground = L"cf"; static const wchar_t* tagColorBackground = L"cb"; //static const wchar_t* tagColorBackgroundWord = L"chcbpat"; //static const wchar_t* tagColorHighlight = L"highlight"; // Some Rhino specific optional tags //static const wchar_t* tagExColorTable = L"expandedcolortbl"; //static const wchar_t* tagTextHeight = L"h"; // not rtf static const wchar_t* tagStackFraction = L"stackheight"; // not rtf - scale factor for text height in stacked text static const wchar_t* tagStackText = L"stack"; // not rtf - begin stacked fraction static const wchar_t* tagStackEnd = L"stnone"; // not rtf - end stacked fraction static const wchar_t* tagField = L"field"; static const wchar_t* tagUniCpCount = L"uc"; // #bytes used following \uN for codepage code of equivalenet char static const wchar_t* tagUniCharDec = L"u"; // UNOCODE UTF-16 encoded value as a signed short (0x8...) will be -.... // NOTE WELL: When a single UNICODE code point requires a UTF-16 // surrogate pair encoding, there will be TWO \uXXXX? values for that code point. // For example, the single UNICODE code point // ON_UnicodeCodePoint::Wastebasket U+1F5D1 (decimal 128465) // The UTF-16 surrogate pair for U+1F5D1 is (0xD83D, 0xDDD1) and this // value in RTF looks like ...{\ltrch \u-10179?\u-8751?}... // -10179 as a signed 2 byte short has the same bits as unsigned short 0xD83D. // -8751 as a signed 2 byte short has the same bits as unsigned short 0xDDD1. // Many "emoji glyphs" UNOCODE code points require UTF-16 surrogate pair encodings. static const wchar_t* tagUniTwoDest = L"upr"; // two embedded unicode destinations static const wchar_t* tagUniDest = L"ud"; // unicode destination // Characters typed with Alt+0123 // \lquote \rquote \ldblquote \rdblquote \bullet \endash \emdash static const wchar_t* taglquote = L"lquote"; // left quote static const wchar_t* tagrquote = L"rquote"; // right quote static const wchar_t* tagldblquote = L"ldblquote"; // left double quote static const wchar_t* tagrdblquote = L"rdblquote"; // right double quote static const wchar_t* tagbullet = L"bullet"; // bullet static const wchar_t* tagendash = L"endash"; // endash static const wchar_t* tagemdash = L"emdash"; // emdash #pragma region TextIterator ON_TextIterator::ON_TextIterator(const ON_wString& str) : m_text(str.Array()) , m_length(str.Length()) { Step(); // Make first character in the string current } ON_TextIterator::ON_TextIterator(const wchar_t* str, size_t length) : m_text(str) , m_length(length) { Step(); // Make first character in the string current } bool ON_TextIterator::Step() { // Get the next UNICODE code point encoded in m_text beginning at m_text[m_next_text_ci]; // Save this code point in m_cur_codepoint. if(m_next_text_ci < m_length) { m_ue.m_error_status = 0; ON__UINT32 codepoint = 0; // Works on Windows, Apple, and any other platform. const int ci = ON_DecodeWideChar((m_text+m_next_text_ci), (int)(m_length-m_next_text_ci), &m_ue, &codepoint); if(ci > 0) { m_prev_text_ci = m_cur_text_ci; m_cur_text_ci = m_next_text_ci; m_next_text_ci = m_next_text_ci+ci; m_prev_codepoint = m_cur_codepoint; m_cur_codepoint = codepoint; return true; } } m_prev_codepoint = m_cur_codepoint; m_cur_codepoint = 0; return false; } bool ON_TextIterator::PeekCodePoint(ON__UINT32& unicode_code_point) const { unicode_code_point = m_cur_codepoint; return (0 != unicode_code_point); } bool ON_TextIterator::ReadCodePoint(ON__UINT32& unicode_code_point) { unicode_code_point = m_cur_codepoint; Step(); return (0 != unicode_code_point); } bool ON_TextIterator::Back() { if(m_prev_text_ci == m_cur_text_ci) { // fancy backup in wchar_t string } m_next_text_ci = m_cur_text_ci; m_cur_text_ci = m_prev_text_ci; m_cur_codepoint = m_prev_codepoint; return true; } static bool Internal_IsHexDigit(ON__UINT32 x) { return ((x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f')); } static ON__UINT32 Internal_IsHexValue(ON__UINT32 x) { if (x >= '0' && x <= '9') return (x - '0'); if (x >= 'A' && x <= 'F') return x - 'A' + 10; if (x >= 'a' && x <= 'f') return x - 'a' + 10; return 0xFFFFFFFF; } bool ON_TextIterator::AtBackslashTic() const { return ( ON_UnicodeCodePoint::ON_Backslash == m_cur_codepoint && m_next_text_ci < m_length && '\'' == m_text[m_next_text_ci] ); } bool ON_TextIterator::ReadCharValue( unsigned char& c ) { for(;;) { ON__UINT32 buffer[4]; if (false == ReadCodePoint(buffer[0])) break; if (ON_UnicodeCodePoint::ON_Backslash != buffer[0]) break; if (false == ReadCodePoint(buffer[1])) break; if ('\'' != buffer[1]) break; if (false == ReadCodePoint(buffer[2])) break; if (false == Internal_IsHexDigit(buffer[2])) break; if (false == ReadCodePoint(buffer[3])) break; if (false == Internal_IsHexDigit(buffer[3])) break; ON__UINT32 x = 16 * Internal_IsHexValue(buffer[2]) + Internal_IsHexValue(buffer[3]); if (x > 255) break; c = (unsigned char)x; return true; } c = 0; return false; } ON__UINT32 ON_RtfParser::Internal_ParseMBCSString( const ON__UINT32 windows_code_page ) { ON__UINT32 count = 0; bool bParsed = false; ON_String mbcs; mbcs.ReserveArray(64); while (m_ti.AtBackslashTic()) { count++; unsigned char c; bParsed = m_ti.ReadCharValue(c); if (false == bParsed) break; mbcs.Append((const char*)(&c), 1); } const char* sMBCS = static_cast(mbcs); int sMBCS_count = mbcs.Length(); wchar_t* sWideChar = nullptr; int sWideChar_capacity = 0; unsigned int error_status = 0; if (nullptr != sMBCS && 0 != sMBCS[0] && sMBCS_count > 0) { const int sWideChar_count0 = ON_ConvertMSMBCPToWideChar( windows_code_page, sMBCS, sMBCS_count, sWideChar, sWideChar_capacity, &error_status ); if (sWideChar_count0 > 0) { sWideChar_capacity = sWideChar_count0 + 2; sWideChar = new wchar_t[sWideChar_capacity]; if (nullptr != sWideChar) { memset(sWideChar, 0, sWideChar_capacity * sizeof(sWideChar[0])); } error_status = 0; const int sWideChar_count1 = ON_ConvertMSMBCPToWideChar( windows_code_page, sMBCS, sMBCS_count, sWideChar, sWideChar_capacity-1, &error_status ); if (sWideChar_count1 > 0 && 0 != sWideChar[0] && 0 == sWideChar[sWideChar_capacity - 1]) { int delta_i = sWideChar_count1; for (int i = 0; i < sWideChar_count1; i += delta_i) { struct ON_UnicodeErrorParameters e = ON_UnicodeErrorParameters::MaskErrors; ON__UINT32 unicode_code_point = ON_UnicodeCodePoint::ON_ReplacementCharacter; delta_i = ON_DecodeWideChar(sWideChar + i, sWideChar_count1 - i, &e, &unicode_code_point); if (delta_i <= 0) { bParsed = false; break; } m_builder.AppendCodePoint(unicode_code_point); } } delete[] sWideChar; sWideChar = nullptr; } } if (false == bParsed) m_builder.AppendCodePoint(ON_UnicodeCodePoint::ON_ReplacementCharacter); return count; } #pragma endregion TextIterator #pragma region TextBuilder ON_TextBuilder::~ON_TextBuilder() {} ON_TextBuilder::ON_TextBuilder() : m_current_codepoints(16) { m_current_UTF16_buffer_count = 0; m_current_UTF16_buffer[0] = 0; m_current_UTF16_buffer[1] = 0; } void ON_TextBuilder::InitBuilder(const ON_Font* default_font) { m_in_run = 0; m_level = 0; m_font_table_level = 100000; m_current_codepoints.Empty(); } void ON_TextBuilder::GroupBegin() { m_level++; } void ON_TextBuilder::GroupEnd() { //if(m_font_table_level >= m_level) // m_font_table_level = -1; m_level--; } void ON_TextBuilder::RunBegin() { } void ON_TextBuilder::RunEnd() { } bool ON_TextBuilder::ReadingFontTable() { return (m_level >= m_font_table_level); } bool ON_TextBuilder::ReadingFontDefinition() { return m_bReadingFontDefinition; } void ON_TextBuilder::SetReadingFontDefinition(bool b) { m_bReadingFontDefinition = b; } void ON_TextBuilder::FinishFontDef() { SetReadingFontDefinition(false); } void ON_TextBuilder::FlushText(size_t count, ON__UINT32* cp) { } void ON_TextBuilder::BeginFontTable() { m_font_table_level = m_level; } void ON_TextBuilder::BeginHeader() { } // Sets m_default_font_index when \deffn is read. void ON_TextBuilder::DefaultFont(const wchar_t* value) { ON__INT32 nval = -1; const wchar_t* sp = ON_wString::ToNumber(value, nval, &nval); if(nval >= 0 && sp > value) m_default_font_index = nval; } // Process a rtf \fn tag to set the current font to the nth font in the rtf font table void ON_TextBuilder::FontTag(const wchar_t* value) { ON__INT32 nval = -1; const wchar_t* sp = ON_wString::ToNumber(value, nval, &nval); if(nval >= 0 && sp > value) { //if(m_font_table_level >= 0 && // m_level >= m_font_table_level) if(ReadingFontTable()) { // Defining a font in the font table // n in \fn is the rtf index of the font currently being read from the table // store a mapping from \fn to font // when the font for this font table entry is made m_font_index = nval; // next will be a face name that will be read and put in a fontdef run } //else //{ // // Not defining the font table. Rather, setting a font current // // Set current font to font corresponding to font_index //} } return; } const ON_wString ON_TextBuilder::FaceNameFromMap(int nval) { int count = m_facename_map.Count(); for (int mi = 0; mi < count; mi++) { if (m_facename_map[mi].m_rtf_font_index == nval) return m_facename_map[mi].m_rtf_font_name; } return ON_wString::EmptyString; } unsigned int ON_TextBuilder::CodePageFromMap(int nval) { int count = m_facename_map.Count(); for (int mi = 0; mi < count; mi++) { if (m_facename_map[mi].m_rtf_font_index == nval) return m_facename_map[mi].m_codepage; } return 1252; } unsigned int ON_TextBuilder::CharSetFromMap(int nval) { int count = m_facename_map.Count(); for (int mi = 0; mi < count; mi++) { if (m_facename_map[mi].m_rtf_font_index == nval) return m_facename_map[mi].m_charset; } return 0; } void ON_TextBuilder::FontSize(const wchar_t* value) { } void ON_TextBuilder::CharSet(const wchar_t* value) // \fcharsetN { unsigned int charset = 0; const wchar_t* sp = ON_wString::ToNumber(value, charset, &charset); if (sp > value) { //if (m_font_table_level >= 0 && m_level >= m_font_table_level) if(ReadingFontDefinition()) { // This is a charset specification in a font definition in the font table // the value is convertable to a codepage to use for interpreting the text chars // using this font into unicode points m_current_props.SetCharSet(charset, true); } } } void ON_TextBuilder::CodePage(const wchar_t* value) // \cpgN { unsigned int codepage = 0; const wchar_t* sp = ON_wString::ToNumber(value, codepage, &codepage); if (sp > value) { //if (m_font_table_level >= 0 && m_level >= m_font_table_level) if(ReadingFontDefinition()) { // This is a codepage specification in a font definition in the font table m_current_props.SetCodePage(codepage); } } } void ON_TextBuilder::Newline() { } void ON_TextBuilder::Paragraph() { } void ON_TextBuilder::ParagraphDefaults() { } void ON_TextBuilder::Section() { } void ON_TextBuilder::Tab() { } void ON_TextBuilder::Bold(const wchar_t* value) { } void ON_TextBuilder::Italic(const wchar_t* value) { } void ON_TextBuilder::UnderlineOn() { } void ON_TextBuilder::UnderlineOff() { } void ON_TextBuilder::Strikethrough(const wchar_t* value) { } void ON_TextBuilder::Superscript() { } void ON_TextBuilder::Subscript() { } void ON_TextBuilder::NoSuperSub() { } void ON_TextBuilder::BeginColorTable() { } void ON_TextBuilder::ColorRed(const wchar_t* value) { } void ON_TextBuilder::ColorGreen(const wchar_t* value) { } void ON_TextBuilder::ColorBlue(const wchar_t* value) { } void ON_TextBuilder::ColorForeground(const wchar_t* value) { } void ON_TextBuilder::ColorBackground(const wchar_t* value) { } void ON_TextBuilder::SetStackScale(const wchar_t* value) { } void ON_TextBuilder::StackFraction(const wchar_t* value) { } void ON_TextBuilder::StackEnd() { } void ON_TextBuilder::TextField(const wchar_t* name) { } ON__UINT32* ON_TextBuilder::RunCodePoints(const ON_TextRun& run) { return run.m_codepoints; } void ON_TextBuilder::UniCpCount(const wchar_t* value) { // Dale Lear April 2018 - This code does absolutely nothing useful- commenting all of it out. //// #bytes used following \uN for codepage code of equivalenet char //short count = 1, d = ON_DBL_QNAN; //const wchar_t* sp = ON_wString::ToNumber(value, d, &d); //if (sp > value) // count = d; } void ON_TextBuilder::UniDecimal(const wchar_t* value) { // UTF-16 encoding value as a decimal, possibly signed negative // unicode char in decimal followed by ascii char(s) (to skip if unicode can't be digested) : // \u1472? means char at unicode point 1472, or '?' as a fallback ascii char to substitute. // the decimal value may be written as a negative number: \u-2671? and should be cast to unsigned. ON__UINT16 u = 0; const wchar_t* sp = nullptr; for (;;) { // For example, the single UNICODE code point ON_UnicodeCodePoint::Wastebasket U+1F5D1 (decimal 128465) // // As of March 2018: // RTF from Windows 10 controls are using signed shorts with question mark as the ASCII fallback char // Wastebasket = "\u-10179?\u-8751?" // \u-10179? -> unsigned short 0xD83D // \u-8751? -> unsigned short 0xDDD1 // UTF-16 surrogate pair is (0xD83D, 0xDDD1) -> U+1F5D1 = UNICODE wastebasket glyph // RTF from MacOS controls are using unsigned shorts with a space as the ASCII fallback char // Wastebasket = "\u55357 \u56785 " // \u55357 -> unsigned short 0xD83D // \u56785 -> unsigned short 0xDDD1 // UTF-16 surrogate pair is (0xD83D, 0xDDD1) -> UNICODE code point U+1F5D1 = wastebasket glyph short signed_short = 0; sp = ON_wString::ToNumber(value, signed_short, &signed_short); if (nullptr != sp && sp > value) { // value is either positive or a negative in short range u = (unsigned short)signed_short; break; } unsigned short unsigned_short = 0; sp = ON_wString::ToNumber(value, unsigned_short, &unsigned_short); if (nullptr != sp && sp > value && unsigned_short > 32767) { // value is a unsigned short too large to be a signed short. u = unsigned_short; break; } if (0 == m_current_UTF16_buffer_count) { // No doubt, we will eventually encounter RTF will using unsigned UTF-32 values. unsigned int u32 = 0; sp = ON_wString::ToNumber(value, u32, &u32); if (nullptr != sp && sp > value && u32 > 0xffff && ON_IsValidUnicodeCodePoint(u32) ) { // value is a unsigned int too large to be an unsigned short. AppendCodePoint(u32); return; } } // probably an error in the RTF or a bug in this parsing code. sp = nullptr; break; } // Often *sp = '?' (question mark) on Windows and ' ' (space) on MacOS, // but that is not required if some other ASCII fallback char is used. if (nullptr != sp && sp > value) { bool bError = false; const ON__UINT32 error_cp = ON_UnicodeCodePoint::ON_ReplacementCharacter; const ON__UINT16 waiting_mark = ON_TextBuilder::m_UFT16_waiting_mark; const ON__UINT16 unused_mark = ON_TextBuilder::m_UFT16_unused_mark; const bool bHaveFirstSurrogate = (1 == m_current_UTF16_buffer_count && m_current_UTF16_buffer[0] >= 0xD800 && m_current_UTF16_buffer[0] < 0xDC00 && waiting_mark == m_current_UTF16_buffer[1]); ON__UINT32 cp = 0; // Mar 3, 2017 Dale Lear // Attempt to support handling of UTF-16 surrogate pairs without // rewriting the RTF parser. // For example, the single UNICODE code point ON_UnicodeCodePoint::Wastebasket U+1F5D1 (decimal 128465) // is in the RTF string as ...{\ltrch \u-10179?\u-8751?}... // The UTF-16 surrogate pair is (0xD83D, 0xDDD1) and this value decodes to the single UNICODE code point U+1F5D1 // \u-10179? -> unsigned short 0xD83D // \u-8751? -> unsigned short 0xDDD1 if (u >= 0xD800 && u < 0xDC00) { if (bHaveFirstSurrogate) { // never got the second value for the pair //m_current_codepoints.Append(error_cp); AppendCodePoint(error_cp); } if (0 == m_current_UTF16_buffer_count) { m_current_UTF16_buffer_count = 1; m_current_UTF16_buffer[0] = u; m_current_UTF16_buffer[1] = waiting_mark; return; // we need the second surrogate pair value before we can decode } bError = true; } else if (u >= 0xDC00 && u < 0xE000) { if (bHaveFirstSurrogate) { m_current_UTF16_buffer_count = 2; m_current_UTF16_buffer[1] = u; } else { // We should have gotten the second value for the pair bError = true; } } else { if (bHaveFirstSurrogate) { // never got the second value for the pair m_current_codepoints.Append(error_cp); } m_current_UTF16_buffer_count = 1; m_current_UTF16_buffer[0] = u; m_current_UTF16_buffer[1] = unused_mark; } if (false == bError) { ON_UnicodeErrorParameters e; e.m_error_code_point = error_cp; e.m_error_mask = 16; e.m_error_status = 0; const int rc = ON_DecodeUTF16(m_current_UTF16_buffer, m_current_UTF16_buffer_count, &e, &cp); bError = (m_current_UTF16_buffer_count != rc || false == ON_IsValidUnicodeCodePoint(cp)); } if (bError) cp = error_cp; //m_current_codepoints.Append(cp); AppendCodePoint(cp); m_current_UTF16_buffer_count = 0; m_current_UTF16_buffer[0] = unused_mark; m_current_UTF16_buffer[1] = unused_mark; } } void ON_TextBuilder::UniEmbeddedDest(const wchar_t* value) { // two embedded unicode destinations - footnote, etc in rtf document } void ON_TextBuilder::UniDest(const wchar_t* value) { // unicode destination } void ON_TextBuilder::LQuote() { m_current_codepoints.Append(0x2018); } void ON_TextBuilder::RQuote() { m_current_codepoints.Append(0x2019); } void ON_TextBuilder::LDblQuote() { m_current_codepoints.Append(0x201c); } void ON_TextBuilder::RDblQuote() { m_current_codepoints.Append(0x201d); } void ON_TextBuilder::Bullet() { m_current_codepoints.Append(0x2022); } void ON_TextBuilder::EnDash() { m_current_codepoints.Append(0x2013); } void ON_TextBuilder::EmDash() { m_current_codepoints.Append(0x2014); } bool ON_TextBuilder::AppendCodePoint(ON__UINT32 codept) { m_current_codepoints.Append(codept); return true; } void ON_TextBuilder::FormatChange() { } #pragma endregion // TextBuilder #pragma region TextRunBuilder ON_TextRunBuilder::~ON_TextRunBuilder() {} ON_TextRunBuilder::ON_TextRunBuilder( ON_TextContent& text, ON_TextRunArray& runs, const ON_DimStyle* dimstyle, double height, ON_Color color) : m_runs(runs) , m_text(text) { m_current_UTF16_buffer_count = 0; m_current_UTF16_buffer[0] = 0; m_current_UTF16_buffer[1] = 0; if (nullptr == dimstyle) dimstyle = &ON_DimStyle::Default; const ON_Font& style_font = dimstyle->Font(); // this is always a managed font double stackscale = dimstyle->StackHeightScale(); ON_DimStyle::stack_format stackformat = dimstyle->StackFractionFormat(); bool bold = dimstyle->Font().IsBoldInQuartet(); bool italic = dimstyle->Font().IsItalic(); bool underlined = dimstyle->Font().IsUnderlined(); bool strikethrough = dimstyle->Font().IsStrikethrough(); this->SetCurrentFont(&style_font); m_current_props.SetColor(color); m_current_props.SetHeight(height); m_current_props.SetStackScale(stackscale); m_current_props.SetStackFormat(stackformat); m_current_props.SetBold(bold); m_current_props.SetItalic(italic); m_current_props.SetUnderlined(underlined); m_current_props.SetStrikethrough(strikethrough); m_current_run.Init( CurrentFont(), height, stackscale, color, bold, italic, underlined, strikethrough); } void ON_TextRunBuilder::InitBuilder(const ON_Font* default_font) { if (nullptr == default_font) default_font = &ON_Font::Default; if (nullptr == default_font) return; this->SetCurrentFont(default_font); // copy of default_font m_in_run = 0; m_level = 0; m_font_table_level = 10000; m_runs = ON_TextRunArray::EmptyArray; m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); // Array for accumulating text codepoints m_current_codepoints.Empty(); } void ON_TextRunBuilder::AppendCurrentRun() { //if(m_current_run.IsValid()) { ON_TextRun* run = ON_TextRun::GetManagedTextRun(m_current_run); if (0 != run) { m_runs.AppendRun(run); } m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); } } bool ON_TextRunBuilder::AppendCodePoint(ON__UINT32 codept) { if (m_current_codepoints.Count() == 0 && m_current_run.IsStacked() != ON_TextRun::Stacked::kStacked) { const ON_TextRun::Stacked stacked = m_current_run.IsStacked(); // First codepoint in a run after format changes m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); if (ON_TextRun::Stacked::kTop == stacked || ON_TextRun::Stacked::kBottom == stacked) { m_current_run.SetStacked(stacked); } } m_current_codepoints.Append(codept); return true; } void ON_TextRunBuilder::FormatChange() { int cp32_count = m_current_codepoints.Count(); if (cp32_count > 0) { FlushText(cp32_count, m_current_codepoints.Array()); m_current_codepoints.Empty(); FinishCurrentRun(); } } #ifndef ON_TEXT_BRACKET_FRACTION static bool FindFrac(const wchar_t* wstr, int start, int spos, int& topstart, int& bottomend) { if (spos < start + 1) return false; int len = (int)wcslen(wstr); if (spos > len - 2) return false; topstart = spos; bottomend = spos; for (int i = spos; i > start && isdigit(wstr[i - 1]); i--) { topstart = i - 1; } bottomend = spos; for (int i = spos; i < len - 1 && isdigit(wstr[i + 1]); i++) { bottomend = i + 1; } if (topstart == spos || bottomend == spos) return false; if (topstart > start + 2 && isdigit(wstr[topstart - 2])) {//drop leading space } if (bottomend < len - 2 && isdigit(wstr[bottomend + 2])) {// drop trailing space } return true; } typedef struct tagFract { int start; int slash; int end; } Fraction; static void FindFractions(ON_TextRun* run, ON_SimpleArray& fractlist) { if (nullptr == run) return; size_t cpcount = ON_TextRun::CodepointCount(run->UnicodeString()); if (0 != cpcount) { ON_wString str; ON_TextContext::ConvertCodepointsToString((int)cpcount, run->UnicodeString(), str); if (!str.IsEmpty()) { const wchar_t* wstr = str.Array(); int len = str.Length(); int start = 0; int topstart = -1; int bottomend = -1; int i = 0; while (i < len && 0 != wstr[i]) { if (wstr[i] == L'/') { if (FindFrac(wstr, start, i, topstart, bottomend)) { Fraction& fract = fractlist.AppendNew(); fract.start = topstart; fract.end = bottomend; fract.slash = i; i = bottomend; start = bottomend + 1; } } i++; } } } } #endif void ON_TextRunBuilder::FinishCurrentRun() { //int cplen = m_current_run.m_codepoint_idx.j - m_current_run.m_codepoint_idx.i; if (m_current_run.Type() == ON_TextRun::RunType::kText || m_current_run.Type() == ON_TextRun::RunType::kField || m_current_run.Type() == ON_TextRun::RunType::kNewline || m_current_run.Type() == ON_TextRun::RunType::kParagraph) { // Finish off the text run - // Text string is already stored in m_codepoints // Find or make a managed font like the current one // and store that pointer on the run if (nullptr == this->CurrentFont()) this->SetCurrentFont(&ON_Font::Default); const ON_Font* pManagedFont = this->CurrentFont()->ManagedFont(); if (nullptr != pManagedFont) { m_current_run.SetFont(pManagedFont); m_current_run.SetColor(m_current_props.Color()); m_current_run.SetTextHeight(m_current_props.Height()); m_current_run.SetStackFractionHeight(m_current_props.StackScale()); } #ifndef ON_TEXT_BRACKET_FRACTION ON_SimpleArray fractlist; int fcount = 0; if (ON_DimStyle::stack_format::None != m_current_props.StackFormat()) { FindFractions(&m_current_run, fractlist); fcount = fractlist.Count(); } if (0 < fcount) { int cpos = 0; int cpcount = (int)ON_TextRun::CodepointCount(m_current_run.m_codepoints); ON__UINT32* cpts = (ON__UINT32*)onmalloc((1 + cpcount) * sizeof(ON__UINT32)); memcpy(cpts, m_current_run.m_codepoints, (1 + cpcount) * sizeof(ON__UINT32)); for (int i = 0; i < fcount; i++) { Fraction& f = fractlist[i]; if (f.start >= cpos) { int k = f.start; // If the last char before the fraction is a space // and the char before that is a digit, drop the space if (f.start > 1 && isdigit(cpts[f.start - 1])) k = f.start - 1; m_current_run.SetUnicodeString(k - cpos, cpts + cpos); AppendCurrentRun(); cpos = f.start; m_current_run.SetUnicodeString(f.end - f.start + 1, cpts + cpos); if (m_current_props.StackFormat() != ON_DimStyle::stack_format::None) m_current_run.SetStacked(ON_TextRun::Stacked::kStacked); else m_current_run.SetStacked(ON_TextRun::Stacked::kNone); AppendCurrentRun(); m_current_run.SetStacked(ON_TextRun::Stacked::kNone); cpos = f.end + 1; } } if (cpos < cpcount) { m_current_run.SetUnicodeString(cpcount - cpos, cpts + cpos); AppendCurrentRun(); } } else #endif // 0 AppendCurrentRun(); } } static bool IsValidFontName(const ON_wString& name) { // Test to prevent making fonts named "(varies)" if (L'(' == name[0] && L')' == name[name.Length()-1]) return false; return true; } void ON_TextRunBuilder::FinishFontDef() { if (ReadingFontDefinition()) { int cp32_count = m_current_codepoints.Count(); if (cp32_count > 0) { FlushText(cp32_count, m_current_codepoints.Array()); m_current_codepoints.Empty(); } // String is a font "facename" (In reality the "facename" it can be a PostScript name, a LOGFONT.lfFaceName, // or a DWrite family name - depending on who is making the RTF). // Make a font with that facename and a font definition run size_t cpcount = ON_TextRun::CodepointCount(RunCodePoints(m_current_run)); if (0 != cpcount) { ON_wString rtf_fontname_string; ON_TextContext::ConvertCodepointsToString((int)cpcount, RunCodePoints(m_current_run), rtf_fontname_string); if (!rtf_fontname_string.IsEmpty()) { rtf_fontname_string.Remove(L';'); // facename delimiter from rtf if (!IsValidFontName(rtf_fontname_string)) { ON_ERROR("Invalid font name found in rtf string"); rtf_fontname_string = ON_Font::Default.RichTextFontName(); if (rtf_fontname_string.IsEmpty()) rtf_fontname_string = L"Arial"; } // Dale Lear - March 2021 - RH-62974 // There are multiple places in the text run, rich text parsing, and Rhino UI code // that need find a font from rich text properties. // They must all do it the same way. If for some reason, you must change this call, // please take the time to create a single function that replaces this call, search // for all the places ON_Font::FontFromRichTextProperties is called in // src4/rhino/... // opennurbs_textrun.cpp // opennurbs_textiterator.cpp // and replace every call to ON_Font::FontFromRichTextProperties with a call to // your new function. // // This needs to be the regular face of the rich text quartet. const ON_Font* managed_font = ON_Font::FontFromRichTextProperties(rtf_fontname_string,false,false,false,false); if (nullptr == managed_font) managed_font = &ON_Font::Default; rtf_fontname_string = managed_font->RichTextFontName(); ON_FaceNameKey& fn_key = m_facename_map.AppendNew(); fn_key.m_rtf_font_index = m_font_index; fn_key.m_rtf_font_name = rtf_fontname_string; fn_key.m_codepage = m_current_props.CodePage(); fn_key.m_charset = m_current_props.CharSet(); } } if (m_current_run.Type() == ON_TextRun::RunType::kFontdef && m_level == m_font_table_level) { if (m_font_stack.Count() > 0 && m_prop_stack.Count() > 0) { this->SetCurrentFont(*m_font_stack.Last()); // pop m_font_stack.Remove(); m_current_props = *m_prop_stack.Last(); // pop m_prop_stack.Remove(); } m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); } SetReadingFontDefinition(false); } } void ON_TextRunBuilder::GroupBegin() // { { int cp32_count = m_current_codepoints.Count(); if (cp32_count > 0) { FlushText(cp32_count, m_current_codepoints.Array()); m_current_codepoints.Empty(); } FinishCurrentRun(); m_level++; // m_current_font starts out as the value from the text object m_font_stack.Append(this->CurrentFont()); // push prev current font m_prop_stack.Append(m_current_props); // push prev current height and color m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); } void ON_TextRunBuilder::GroupEnd() // '}' { int cp32_count = m_current_codepoints.Count(); if (cp32_count > 0) { FlushText(cp32_count, m_current_codepoints.Array()); m_current_codepoints.Empty(); } FinishCurrentRun(); // Pop font and set up for next run if (m_font_stack.Count() > 0 && m_prop_stack.Count() > 0) { this->SetCurrentFont(*m_font_stack.Last()); // pop m_font_stack.Remove(); m_current_props = *m_prop_stack.Last(); // pop m_prop_stack.Remove(); } m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); if (m_level <= m_font_table_level) m_font_table_level = 10000; m_level--; } void ON_TextRunBuilder::RunBegin() // like { with no pushing properties { int cp32_count = m_current_codepoints.Count(); if (cp32_count > 0) { FlushText(cp32_count, m_current_codepoints.Array()); m_current_codepoints.Empty(); } FinishCurrentRun(); m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); } void ON_TextRunBuilder::RunEnd() // like '}' with no popping properties { int cp32_count = m_current_codepoints.Count(); if (cp32_count > 0) { FlushText(cp32_count, m_current_codepoints.Array()); m_current_codepoints.Empty(); } FinishCurrentRun(); m_current_run.Init(this->CurrentFont(), m_current_props.Height(), m_current_props.StackScale(), m_current_props.Color(), m_current_props.IsBold(), m_current_props.IsItalic(), m_current_props.IsUnderlined(), m_current_props.IsStrikethrough()); if (m_level <= m_font_table_level) m_font_table_level = 10000; } void ON_TextRunBuilder::FlushText(size_t count, ON__UINT32* cp) { if (count < 1 || 0 == cp || 0 == cp[0]) return; m_current_run.SetUnicodeString(count, cp); if(ReadingFontDefinition()) { // String is a rich text facename. Make a font from that rich text facename and a font definition run m_current_run.SetType(ON_TextRun::RunType::kFontdef); ON_wString str; ON_TextContext::ConvertCodepointsToString((int)count, cp, str); if (!str.IsEmpty()) { str.Remove(L';'); // facename delimiter from rtf m_current_run.SetType(ON_TextRun::RunType::kFontdef); // This is a face name in the rich text font definition table. // We do not have an bold/italic/underline/strikethrough attributes to apply at this stage. const ON_Font* rtf_fonttbl_font = ON_Font::FontFromRichTextProperties(str,false,false,false,false); if (nullptr != rtf_fonttbl_font) this->SetCurrentFont(rtf_fonttbl_font); } } else { // String is for a text run m_current_run.SetType(ON_TextRun::RunType::kText); } #if defined(ON_DEBUG) && defined(ON_COMPILER_MSC) if (m_current_run.IsStacked() == ON_TextRun::Stacked::kStacked) count = count; // good place for a breakpoint #endif } void ON_TextRunBuilder::BeginFontTable() { m_font_table_level = m_level; } void ON_TextRunBuilder::BeginHeader() { m_current_run.SetType(ON_TextRun::RunType::kHeader); } // Sets m_default_font_index when \deffn is read. void ON_TextRunBuilder::DefaultFont(const wchar_t* value) { ON__INT32 nval = -1; const wchar_t* sp = ON_wString::ToNumber(value, nval, &nval); if (nval >= 0 && sp > value) m_default_font_index = nval; } static const ON_Font* Internal_UpdateManagedFont( const ON_Font* current_managed_font, const ON_wString rtf_font_name, bool rtf_bBold, bool rtf_bItalic, bool rtf_bUnderlined, bool rtf_bStrikethrough ) { // insure bool compares work as expected. if (false != rtf_bBold) rtf_bBold = true; if (false != rtf_bItalic) rtf_bItalic = true; if (false != rtf_bUnderlined) rtf_bUnderlined = true; if (false != rtf_bStrikethrough) rtf_bStrikethrough = true; if (nullptr != current_managed_font && false == current_managed_font->IsManagedFont()) current_managed_font = current_managed_font->ManagedFont(); if (nullptr == current_managed_font) current_managed_font = &ON_Font::Default; ON_wString local_rtf_name(rtf_font_name); local_rtf_name.TrimLeftAndRight(); if (local_rtf_name.IsEmpty()) local_rtf_name = ON_Font::RichTextFontName(current_managed_font, true); const ON_Font* new_managed_font = nullptr; const bool bChangeFont = ON_wString::EqualOrdinal(local_rtf_name, ON_Font::RichTextFontName(current_managed_font, false), true) ? false // rich text face name did not change : true // rich text face name changed. ; const bool bChangeBold = (rtf_bBold?1:0) != (current_managed_font->IsBoldInQuartet()?1:0); const bool bChangeItalic = (rtf_bItalic ? 1 : 0) != (current_managed_font->IsItalicInQuartet() ? 1 : 0); const bool bChangeUnderlined = (rtf_bUnderlined?1:0) != (current_managed_font->IsUnderlined()?1:0); const bool bChangeStrikethrough = (rtf_bStrikethrough?1:0) != (current_managed_font->IsStrikethrough()?1:0); if (bChangeFont || bChangeBold || bChangeItalic) { // Dale Lear - March 2021 - RH-62974 // There are multiple places in the text run, rich text parsing, and Rhino UI code // that need find a font from rich text properties. // They must all do it the same way. If for some reason, you must change this call, // please take the time to create a single function that replaces this call, search // for all the places ON_Font::FontFromRichTextProperties is called in // src4/rhino/... // opennurbs_textrun.cpp // opennurbs_textiterator.cpp // and replace every call to ON_Font::FontFromRichTextProperties with a call to // your new function. // // // Changing rich text quartet or member in some way must always // be done using ON_Font::FontFromRichTextProperties(). // Otherwise this code will not work right for missing fonts. new_managed_font = ON_Font::FontFromRichTextProperties( local_rtf_name, rtf_bBold, rtf_bItalic, rtf_bUnderlined, rtf_bStrikethrough ); } else if (bChangeUnderlined || bChangeStrikethrough) { // The same rich text quartet member with different underline/strikethrough rendering effects. ON_Font f(*current_managed_font); f.SetUnderlined(rtf_bUnderlined); f.SetStrikethrough(rtf_bStrikethrough); new_managed_font = f.ManagedFont(); } if (nullptr != new_managed_font) return new_managed_font; return current_managed_font; } // Process a rtf \fn tag to set the current font to the nth font in the rtf font table void ON_TextRunBuilder::FontTag(const wchar_t* value) { ON__INT32 nval = -1; const wchar_t* sp = ON_wString::ToNumber(value, nval, &nval); if (nval >= 0 && sp > value) { if(ReadingFontTable()) { SetReadingFontDefinition(true); // Defining a font in the font table // n in \fn is the rtf index of the font currently being read from the table // store a mapping from \fn to font // when the font for this font table entry is made m_font_index = nval; // next will be a face name that will be read and put in a fontdef run } else { // Not defining the font table. Rather, setting a font current // Set current font to font corresponding to font_index //const ON_Font* pManagedFont = FindFontInMap(nval); const ON_Font* pManagedFont = (nullptr == this->CurrentFont()) ? &ON_Font::Default : this->CurrentFont()->ManagedFont() ; ON_wString rft_font_name = FaceNameFromMap(nval); rft_font_name.TrimLeftAndRight(); const bool rtf_bBold = m_current_props.IsBold(); const bool rtf_bItalic = m_current_props.IsItalic(); const bool rtf_bUnderlined = m_current_props.IsUnderlined(); const bool rtf_bStrikethrough = m_current_props.IsStrikethrough(); pManagedFont = Internal_UpdateManagedFont( pManagedFont, rft_font_name, rtf_bBold, rtf_bItalic, rtf_bUnderlined, rtf_bStrikethrough ); if (nullptr != pManagedFont) { this->SetCurrentFont(pManagedFont); // Use the rtf settings to update m_current_props // to because the current computer may not have an exact font match. m_current_props.SetBold(rtf_bBold); m_current_props.SetItalic(rtf_bItalic); m_current_props.SetUnderlined(rtf_bUnderlined); m_current_props.SetStrikethrough(rtf_bStrikethrough); } unsigned int charset = CharSetFromMap(nval); m_current_props.SetCharSet(charset, true); } } return; // error - need font index } void ON_TextRunBuilder::FontSize(const wchar_t* value) { // Even though there is supposed to be a way // to decode control font height to 3d text height, // This always uses the TextHeight from the ON_TextContent object m_current_run.SetTextHeight(m_current_props.Height()); //wchar_t* eptr = const_cast< wchar_t* >(value); //ON__UINT32 fs = ON_wString::ToNumber(value, &eptr, 10); //double d = 1.0; //if(eptr != value && fs > 0) // d = MapControlFontSizeTo3dFontSize(fs); //m_current_run.SetHeight(d); //m_current_props.m_height = d; } void ON_TextRunBuilder::Newline() { m_current_run.SetType(ON_TextRun::RunType::kNewline); } void ON_TextRunBuilder::Paragraph() { m_current_run.SetType(ON_TextRun::RunType::kParagraph); } void ON_TextRunBuilder::ParagraphDefaults() { } void ON_TextRunBuilder::Section() { } void ON_TextRunBuilder::Tab() { for(int i = 0; i < 8; i++) AppendCodePoint((ON__UINT32)' '); m_in_run = true; } void ON_TextRunBuilder::Bold(const wchar_t* value) { bool on = true; if (nullptr != value) { if ('1' == value[0] || 0 == value[0]) on = true; else if ('0' == value[0]) on = false; } if (nullptr == this->CurrentFont()) this->SetCurrentFont(&ON_Font::Default); const ON_Font* current_font = this->CurrentFont(); if (false == current_font->IsManagedFont() || on != current_font->IsBoldInQuartet()) { const ON_Font* managed_font = this->CurrentFont()->ManagedFamilyMemberWithRichTextProperties( on, current_font->IsItalicInQuartet(), current_font->IsUnderlined(), current_font->IsStrikethrough() ); if (nullptr != managed_font) this->SetCurrentFont(managed_font); } m_current_props.SetBold(on); } void ON_TextRunBuilder::Italic(const wchar_t* value) { bool on = true; if (nullptr != value) { if ('1' == value[0] || 0 == value[0]) on = true; else if ('0' == value[0]) on = false; } if (nullptr == this->CurrentFont()) this->SetCurrentFont(&ON_Font::Default); const ON_Font* current_font = this->CurrentFont(); if (false == current_font->IsManagedFont() || on != current_font->IsItalicInQuartet()) { const ON_Font* managed_font = current_font->ManagedFamilyMemberWithRichTextProperties( current_font->IsBoldInQuartet(), on, current_font->IsUnderlined(), current_font->IsStrikethrough() ); if (nullptr != managed_font) this->SetCurrentFont(managed_font); } m_current_props.SetItalic(on); } void ON_TextRunBuilder::UnderlineOn() { if (nullptr == this->CurrentFont()) this->SetCurrentFont(&ON_Font::Default); const ON_Font* current_font = this->CurrentFont(); if ( false == current_font->IsManagedFont() || false == current_font->IsUnderlined() ) { const ON_Font* managed_font = current_font->ManagedFamilyMemberWithRichTextProperties( current_font->IsBoldInQuartet(), current_font->IsItalicInQuartet(), true, current_font->IsStrikethrough() ); if (nullptr != managed_font) this->SetCurrentFont(managed_font); } m_current_props.SetUnderlined(true); } void ON_TextRunBuilder::UnderlineOff() { if (nullptr == this->CurrentFont()) this->SetCurrentFont(&ON_Font::Default); const ON_Font* current_font = this->CurrentFont(); if ( false == current_font->IsManagedFont() || current_font->IsUnderlined() ) { const ON_Font* managed_font = current_font->ManagedFamilyMemberWithRichTextProperties( current_font->IsBoldInQuartet(), current_font->IsItalicInQuartet(), false, current_font->IsStrikethrough() ); if (nullptr != managed_font) this->SetCurrentFont(managed_font); } m_current_props.SetUnderlined(false); } void ON_TextRunBuilder::Strikethrough(const wchar_t* value) { bool on = true; if (nullptr != value) { if ('1' == value[0] || 0 == value[0]) on = true; else if ('0' == value[0]) on = false; } if (nullptr == this->CurrentFont()) this->SetCurrentFont(&ON_Font::Default); const ON_Font* current_font = this->CurrentFont(); if ( false == current_font->IsManagedFont() || on != current_font->IsStrikethrough() ) { const ON_Font* managed_font = current_font->ManagedFamilyMemberWithRichTextProperties( current_font->IsBoldInQuartet(), current_font->IsItalicInQuartet(), current_font->IsUnderlined(), on ); if (nullptr != managed_font) this->SetCurrentFont(managed_font); } m_current_props.SetStrikethrough(on); } void ON_TextRunBuilder::Superscript() { m_current_run.SetStacked(ON_TextRun::Stacked::kTop); } void ON_TextRunBuilder::Subscript() { m_current_run.SetStacked(ON_TextRun::Stacked::kBottom); } void ON_TextRunBuilder::NoSuperSub() { } void ON_TextRunBuilder::BeginColorTable() { } void ON_TextRunBuilder::ColorRed(const wchar_t* value) { } void ON_TextRunBuilder::ColorGreen(const wchar_t* value) { } void ON_TextRunBuilder::ColorBlue(const wchar_t* value) { } void ON_TextRunBuilder::ColorForeground(const wchar_t* value) { } void ON_TextRunBuilder::ColorBackground(const wchar_t* value) { } void ON_TextRunBuilder::SetStackScale(const wchar_t* value) { double stackscale = ON_TextRun::DefaultStackFractionHeight(); ON_wString::ToNumber(value, stackscale, &stackscale); m_current_run.SetStackFractionHeight(stackscale); m_current_props.SetStackScale(stackscale); } void ON_TextRunBuilder::StackFraction(const wchar_t* value) { m_current_run.SetType(ON_TextRun::RunType::kText); m_current_run.SetStacked(ON_TextRun::Stacked::kStacked); if (nullptr != m_current_run.m_stacked_text) delete m_current_run.m_stacked_text; m_current_run.m_stacked_text = new ON_StackedText; if (nullptr != value) m_current_run.m_stacked_text->m_separator = (wchar_t)wcstol(value, 0, 10); else m_current_run.m_stacked_text->m_separator = L'/'; } void ON_TextRunBuilder::StackEnd() { m_current_run.SetStackedOff(); } void ON_TextRunBuilder::TextField(const wchar_t* name) { m_current_run.SetType(ON_TextRun::RunType::kField); } void ON_TextRunBuilder::UniEmbeddedDest(const wchar_t* value) { // two embedded unicode destinations - footnote, etc in rtf document } void ON_TextRunBuilder::UniDest(const wchar_t* value) { // unicode destination } #pragma endregion // TextRunBuilder #pragma region RtfStringBuilder ON_RtfStringBuilder::~ON_RtfStringBuilder() {} ON_RtfStringBuilder::ON_RtfStringBuilder( const ON_DimStyle* dimstyle, double height, ON_Color color) { InitStringBuilder(dimstyle); } void ON_RtfStringBuilder::InitStringBuilder(const ON_DimStyle* dimstyle) { if (nullptr == dimstyle) dimstyle = &ON_DimStyle::Default; const ON_Font& style_font = dimstyle->Font(); ON_FaceNameKey& fnkey = m_facename_map.AppendNew(); fnkey.m_rtf_font_name = ON_Font::RichTextFontName(&style_font, true); fnkey.m_rtf_font_index = 0; bool bold = style_font.IsBoldInQuartet(); bool italic = style_font.IsItalic(); bool underlined = style_font.IsUnderlined(); bool strikethrough = style_font.IsStrikethrough(); m_run_stack.Empty(); m_current_run.SetFontIndex(fnkey.m_rtf_font_index); m_current_run.SetBold(bold); m_current_run.SetItalic(italic); m_current_run.SetUnderlined(underlined); m_current_run.SetStrikeThrough(strikethrough); m_in_run = 0; m_level = 0; m_font_table_level = 10000; m_current_codepoints.Empty(); } void ON_RtfStringBuilder::PushRun(TextRun& run) { m_run_stack.Append(run); } ON_RtfStringBuilder::TextRun ON_RtfStringBuilder::PopRun() { if (m_run_stack.Count() > 0) { TextRun run = *m_run_stack.Last(); m_run_stack.Remove(); return run; } return m_current_run; } bool ON_RtfStringBuilder::InColorTable() { return m_in_color_table; } void ON_RtfStringBuilder::SetInColorTable(bool b) { m_in_color_table = b; } void ON_RtfStringBuilder::SetSkipColorTbl(bool b) { m_skip_color_tbl = b; } void ON_RtfStringBuilder::SetSkipBold(bool b) { m_skip_bold = b; } void ON_RtfStringBuilder::SetSkipItalic(bool b) { m_skip_italic = b; } void ON_RtfStringBuilder::SetSkipUnderline(bool b) { m_skip_underline = b; } void ON_RtfStringBuilder::SetSkipFacename(bool b) { m_skip_facename = b; } bool ON_RtfStringBuilder::SkipColorTbl() { return m_skip_color_tbl; } bool ON_RtfStringBuilder::SkipBold() { return m_skip_bold; } bool ON_RtfStringBuilder::SkipItalic() { return m_skip_italic; } bool ON_RtfStringBuilder::SkipUnderline() { return m_skip_underline; } bool ON_RtfStringBuilder::SkipFacename() { return m_skip_facename; } void ON_RtfStringBuilder::SetMakeBold(bool b) { if(b) m_skip_bold = true; m_make_bold = b; } void ON_RtfStringBuilder::SetMakeItalic(bool b) { if(b) m_skip_italic = true; m_make_italic = b; } void ON_RtfStringBuilder::SetMakeUnderline(bool b) { if (b) m_skip_underline = true; m_make_underline = b; } void ON_RtfStringBuilder::SetMakeFacename(bool b) { if (b) m_skip_facename = true; m_make_facename = b; } bool ON_RtfStringBuilder::MakeBold() { return m_make_bold; } bool ON_RtfStringBuilder::MakeItalic() { return m_make_italic; } bool ON_RtfStringBuilder::MakeUnderline() { return m_make_underline; } bool ON_RtfStringBuilder::MakeFacename() { return m_make_facename; } void ON_RtfStringBuilder::SetDefaultFacename(const wchar_t* facename) { m_default_facename = (nullptr == facename) ? L"" : facename; } void ON_RtfStringBuilder::SetOverrideFacename(const wchar_t* facename) { m_override_facename = (nullptr == facename) ? L"" : facename; } bool ON_RtfStringBuilder::SkippingFacename() { return (m_skip_facename && !m_make_facename); } bool ON_RtfStringBuilder::SettingFacename() { return (m_make_facename && !m_default_facename.IsEmpty()); } void ON_RtfStringBuilder::GroupBegin() { // not skipping the color table or we're not in it if (!SkipColorTbl() || m_current_run.Type() != ON_TextRun::RunType::kColortbl) m_string_out += m_current_run.TextString(); m_current_run.EmptyText(); m_current_run.SetTerminated(true); PushRun(m_current_run); m_current_run.AddControl(L"{"); m_current_run.SetType(ON_TextRun::RunType::kText); m_current_run.ClearHasContent(); m_have_rtf = true; m_level++; } void ON_RtfStringBuilder::GroupEnd() { if (m_current_run.Type() != ON_TextRun::RunType::kColortbl) { if (m_level >= 0) { m_current_run.AddControl(L"}"); m_level--; if (m_current_run.Type() == ON_TextRun::RunType::kFonttbl) { if (SkippingFacename()) { m_current_run.AddControl(L"{\\f0 "); m_level++; } else if (SettingFacename()) { // Add \\fn ofter font table run ON_wString temp; temp.Format(L"{\\f%d ", m_font_index); m_current_run.AddControl(temp.Array()); m_level++; } m_font_table_level = 10000; } m_string_out = m_string_out + m_current_run.TextString(); m_current_run.EmptyText(); } } if (m_current_run.Type() == ON_TextRun::RunType::kColortbl) SetInColorTable(false); m_current_run = PopRun(); } void ON_RtfStringBuilder::RunBegin() { // not skipping the color table or we're not in it if (!SkipColorTbl() || m_current_run.Type() != ON_TextRun::RunType::kColortbl) m_string_out += m_current_run.TextString(); m_current_run.EmptyText(); m_current_run.SetTerminated(true); m_current_run.SetType(ON_TextRun::RunType::kText); m_current_run.ClearHasContent(); m_have_rtf = true; } void ON_RtfStringBuilder::RunEnd() { if (m_current_run.Type() != ON_TextRun::RunType::kColortbl) { if (m_level >= 0) { if (m_current_run.Type() == ON_TextRun::RunType::kFonttbl) { m_font_table_level = 10000; } m_string_out = m_string_out + m_current_run.TextString(); m_current_run.EmptyText(); } } if (m_current_run.Type() == ON_TextRun::RunType::kColortbl) SetInColorTable(false); } void ON_RtfStringBuilder::BeginFontTable() { m_font_table_level = m_level; m_current_run.SetType(ON_TextRun::RunType::kFonttbl); if (SkippingFacename()) return; ON_wString temp; //temp.Format(L"\\fonttbl{\\f0 %s;}", m_default_facename.Array()); temp.Format(L"\\fonttbl"); m_current_run.AddText(temp.Array()); if (SettingFacename() && !m_default_facename.EqualOrdinal(m_override_facename, true)) { temp.Format(L"{\\f1 %s;}", m_override_facename.Array()); m_current_run.AddText(temp.Array()); } } void ON_RtfStringBuilder::BeginHeader() { m_current_run.SetType(ON_TextRun::RunType::kHeader); m_current_run.AddControl(L"\\rtf1"); } // Sets m_default_font_index when \deffn is read. void ON_RtfStringBuilder::DefaultFont(const wchar_t* value) { if (m_skip_facename || (m_make_facename && !m_default_facename.IsEmpty())) m_default_font_index = 0; else { ON__INT32 nval = -1; const wchar_t* sp = ON_wString::ToNumber(value, nval, &nval); if (nval >= 0 && sp > value) m_default_font_index = nval; } ON_wString temp; temp.Format(L"\\deff%d", m_default_font_index); m_current_run.AddControl(temp.Array()); } const ON_wString ON_RtfStringBuilder::OutputString() { m_string_out.Replace(L"{}", L""); return m_string_out; } // Process a rtf \fn tag to set the current font to the nth font in the rtf font table // or to store the definition for the nth font in the font table void ON_RtfStringBuilder::FontTag(const wchar_t* value) { if (SkippingFacename()) return; ON__INT32 nval = -1; const wchar_t* sp = ON_wString::ToNumber(value, nval, &nval); if (nval >= 0 && sp > value) // got a number { if (ReadingFontTable()) { if (m_current_run.Type() == ON_TextRun::RunType::kFonttbl && m_level == m_font_table_level) { m_string_out += m_current_run.TextString(); m_current_run.EmptyText(); m_current_run.SetTerminated(true); PushRun(m_current_run); m_have_rtf = true; } m_current_run.SetType(ON_TextRun::RunType::kFontdef); if (!SettingFacename()) { // Defining a font in the font table // n in \fn is the rtf index of the font currently being read from the table // store a mapping from \fn to font // when the font for this font table entry is made m_font_index = nval; // next will be a face name that will be read and put in a fontdef run ON_wString temp; temp.Format(L"\\f%d", nval); m_current_run.AddControl(temp.Array()); } } else { // Not defining the font table. Rather, setting a font current // Set current font to font corresponding to font_index if (SkippingFacename() || SettingFacename()) m_current_run.AddControl(L"\\f1"); else if (m_current_run.FontIndex() != nval) { ON_wString temp; temp.Format(L"\\f%d", nval); m_current_run.AddControl(temp.Array()); m_current_run.SetFontIndex(nval); } if (MakeBold()) m_current_run.AddControl(L"\\b"); if (MakeItalic()) m_current_run.AddControl(L"\\i"); if (MakeUnderline()) m_current_run.AddControl(L"\\ul"); } } return; } void ON_RtfStringBuilder::FontSize(const wchar_t* value) { // We ignore font height ON__INT32 nval = -1; const wchar_t* sp = ON_wString::ToNumber(value, nval, &nval); if (nval >= 0 && sp > value) // got a number { ON_wString temp; temp.Format(L"\\fs%d", nval); m_current_run.AddControl(temp.Array()); } } void ON_RtfStringBuilder::Newline() { m_current_run.AddControl(L"\\par"); } void ON_RtfStringBuilder::Paragraph() { m_current_run.AddControl(L"\\par"); } void ON_RtfStringBuilder::ParagraphDefaults() { m_current_run.AddControl(L"\\pard"); } void ON_RtfStringBuilder::Section() { } void ON_RtfStringBuilder::Tab() { for (int i = 0; i < 8; i++) AppendCodePoint((ON__UINT32)' '); m_in_run = true; } void ON_RtfStringBuilder::Bold(const wchar_t* value) { if (!SkipBold()) { bool bold = m_current_run.IsBold(); bool on = true; if (nullptr != value) { if ('1' == value[0] || 0 == value[0]) on = true; else if ('0' == value[0]) on = false; } if (on != bold) { ON_wString temp; if ('0' == value[0]) temp.Format(L"\\b0"); else temp.Format(L"\\b"); m_current_run.AddControl(temp.Array()); m_current_run.SetBold(on); } } } void ON_RtfStringBuilder::Italic(const wchar_t* value) { if (!SkipItalic()) { bool italic = m_current_run.IsItalic(); bool on = true; if (nullptr != value) { if ('1' == value[0] || 0 == value[0]) on = true; else if ('0' == value[0]) on = false; } if (on != italic) { ON_wString temp; if ('0' == value[0]) temp.Format(L"\\i0"); else temp.Format(L"\\i"); m_current_run.AddControl(temp.Array()); m_current_run.SetItalic(on); } } } void ON_RtfStringBuilder::UnderlineOn() { if (!SkipUnderline()) { bool under = m_current_run.IsUnderlined(); bool on = true; if (on != under) { m_current_run.AddControl(L"\\ul"); m_current_run.SetUnderlined(true); } } } void ON_RtfStringBuilder::UnderlineOff() { if (!SkipUnderline()) { bool under = m_current_run.IsUnderlined(); bool on = false; if (on != under) { m_current_run.AddControl(L"\\ul0"); m_current_run.SetUnderlined(true); } } } void ON_RtfStringBuilder::Strikethrough(const wchar_t* value) { } void ON_RtfStringBuilder::Superscript() { } void ON_RtfStringBuilder::Subscript() { } void ON_RtfStringBuilder::NoSuperSub() { } void ON_RtfStringBuilder::BeginColorTable() { m_current_run.SetType(ON_TextRun::RunType::kColortbl); m_current_run.AddControl(L"\\colortbl"); SetInColorTable(true); } void ON_RtfStringBuilder::ColorRed(const wchar_t* value) { } void ON_RtfStringBuilder::ColorGreen(const wchar_t* value) { } void ON_RtfStringBuilder::ColorBlue(const wchar_t* value) { } void ON_RtfStringBuilder::ColorForeground(const wchar_t* value) { } void ON_RtfStringBuilder::ColorBackground(const wchar_t* value) { } void ON_RtfStringBuilder::TextField(const wchar_t* name) { m_current_run.SetType(ON_TextRun::RunType::kField); } void ON_RtfStringBuilder::UniEmbeddedDest(const wchar_t* value) { // two embedded unicode destinations - footnote, etc in rtf document } void ON_RtfStringBuilder::UniDecimal(const wchar_t* value) { m_current_run.AddText(L"\\u"); m_current_run.AddText(value); m_current_run.AddText(L"?"); } void ON_RtfStringBuilder::UniDest(const wchar_t* value) { // unicode destination } bool ON_RtfStringBuilder::AppendCodePoint(ON__UINT32 codept) { if ((SettingFacename() || SkippingFacename()) && m_current_run.Type() == ON_TextRun::RunType::kFontdef) return true; if (m_current_run.Type() == ON_TextRun::RunType::kText) { if (MakeBold() && !m_current_run.IsBold()) { m_current_run.AddControl(L"\\b"); m_current_run.SetBold(true); } if (MakeItalic() && !m_current_run.IsItalic()) { m_current_run.AddControl(L"\\i"); m_current_run.SetItalic(true); } if (MakeUnderline() && !m_current_run.IsUnderlined()) { m_current_run.AddControl(L"\\ul"); m_current_run.SetUnderlined(true); } m_have_rtf = true; } ON_wString str; ON_TextContext::ConvertCodepointsToString(1, &codept, str); if (codept == ON_UnicodeCodePoint::ON_Backslash || codept == L'{' || codept == L'}') m_current_run.AddText(L"\\"); m_current_run.AddText(str.Array()); m_current_codepoints.Append(codept); return true; } #pragma endregion // RtfStringBuilder #ifdef RTFFIRSTCHAR #pragma region RtfFirstChar ON_RtfFirstChar::~ON_RtfFirstChar() {} ON_RtfFirstChar::ON_RtfFirstChar( const ON_DimStyle* dimstyle, double height, ON_Color color) { InitStringBuilder(dimstyle); } void ON_RtfFirstChar::InitStringBuilder(const ON_DimStyle* dimstyle) { if (nullptr == dimstyle) dimstyle = &ON_DimStyle::Default; const ON_Font& style_font = dimstyle->Font(); ON_FaceNameKey& fnkey = m_facename_map.AppendNew(); fnkey.m_rtf_font_name = style_font.FamilyName(); fnkey.m_rtf_font_index = -1; bool bold = dimstyle->Font().IsBoldInQuartet(); bool italic = dimstyle->Font().IsItalic(); bool underlined = dimstyle->Font().IsUnderlined(); bool strikethrough = dimstyle->Font().IsStrikethrough(); m_run_stack.Empty(); m_current_run.SetFontIndex(fnkey.m_rtf_font_index); m_current_run.SetBold(bold); m_current_run.SetItalic(italic); m_current_run.SetUnderlined(underlined); m_current_run.SetStrikeThrough(strikethrough); m_in_run = 0; m_level = 0; m_font_table_level = 10000; } void ON_RtfFirstChar::PushRun(TextRun& run) { m_run_stack.Append(run); } ON_RtfFirstChar::TextRun ON_RtfFirstChar::PopRun() { if (m_run_stack.Count() > 0) { TextRun run = *m_run_stack.Last(); m_run_stack.Remove(); return run; } return m_current_run; } bool ON_RtfFirstChar::InColorTable() { return m_in_color_table; } void ON_RtfFirstChar::SetInColorTable(bool b) { m_in_color_table = b; } void ON_RtfFirstChar::GroupBegin() { PushRun(m_current_run); m_have_rtf = true; m_level++; } void ON_RtfFirstChar::GroupEnd() { if (m_current_run.Type() == ON_TextRun::RunType::kFonttbl) m_font_table_level = 10000; if (m_current_run.Type() == ON_TextRun::RunType::kColortbl) SetInColorTable(false); if (m_current_run.Type() == ON_TextRun::RunType::kFontdef) { // String is a font facename. Make a font with that facename // and a font definition run ON_wString str; str = m_current_run.Text(); if (!str.IsEmpty()) { str.Remove(L';'); // facename delimiter from rtf ON_FaceNameKey& fn_key = m_facename_map.AppendNew(); fn_key.m_rtf_font_index = m_font_index; fn_key.m_rtf_font_name = str; fn_key.m_charset = m_current_props.CharSet(); fn_key.m_codepage = m_current_props.CodePage(); } } m_current_run = PopRun(); m_level--; } void ON_RtfFirstChar::RunBegin() { m_have_rtf = true; } void ON_RtfFirstChar::RunEnd() { if (m_current_run.Type() == ON_TextRun::RunType::kFontdef) { // String is a font facename. Make a font with that facename // and a font definition run ON_wString str; str = m_current_run.Text(); if (!str.IsEmpty()) { str.Remove(L';'); // facename delimiter from rtf ON_FaceNameKey& fn_key = m_facename_map.AppendNew(); fn_key.m_rtf_font_index = m_font_index; fn_key.m_rtf_font_name = str; fn_key.m_charset = m_current_props.CharSet(); fn_key.m_codepage = m_current_props.CodePage(); } } } void ON_RtfFirstChar::BeginFontTable() { m_font_table_level = m_level; m_current_run.SetType(ON_TextRun::RunType::kFonttbl); } void ON_RtfFirstChar::BeginHeader() { m_current_run.SetType(ON_TextRun::RunType::kHeader); } void ON_RtfFirstChar::BeginColorTable() { m_current_run.SetType(ON_TextRun::RunType::kColortbl); SetInColorTable(true); } void ON_RtfFirstChar::TextField(const wchar_t* name) { m_current_run.SetType(ON_TextRun::RunType::kField); } bool ON_RtfFirstChar::AppendCodePoint(ON__UINT32 codept) { if (!m_have_rtf) m_have_rtf = true; // First char not in a table or tag - return false to stop parsing if (!InColorTable() && !ReadingFontTable()) return false; ON_wString str; ON_TextContext::ConvertCodepointsToString(1, &codept, str); m_current_run.AddText(str.Array()); m_current_codepoints.Append(codept); return true; } void ON_RtfFirstChar::FontTag(const wchar_t* value) { if (ReadingFontTable()) m_current_run.SetType(ON_TextRun::RunType::kFontdef); ON__INT32 nval = -1; const wchar_t* sp = ON_wString::ToNumber(value, nval, &nval); if (nval >= 0 && sp > value) // got a number { if (ReadingFontTable()) { SetReadingFontDefinition(true); // Defining a font in the font table // n in \fn is the rtf index of the font currently being read from the table // store a mapping from \fn to font // when the font for this font table entry is made m_font_index = nval; // next will be a face name that will be read and put in a fontdef run } else { // Not defining the font table. Rather, setting a font current // Set current font to font corresponding to font_index if (m_current_run.FontIndex() != nval) m_current_run.SetFontIndex(nval); } } return; } void ON_RtfFirstChar::Bold(const wchar_t* value) { bool bold = m_current_run.IsBold(); bool on = true; if (nullptr != value) { if ('1' == value[0] || 0 == value[0]) on = true; else if ('0' == value[0]) on = false; } if (on != bold) { m_current_run.SetBold(on); } } void ON_RtfFirstChar::Italic(const wchar_t* value) { bool italic = m_current_run.IsItalic(); bool on = true; if (nullptr != value) { if ('1' == value[0] || 0 == value[0]) on = true; else if ('0' == value[0]) on = false; } if (on != italic) m_current_run.SetItalic(on); } void ON_RtfFirstChar::UnderlineOn() { bool under = m_current_run.IsUnderlined(); bool on = true; if (on != under) m_current_run.SetUnderlined(true); } void ON_RtfFirstChar::UnderlineOff() { bool under = m_current_run.IsUnderlined(); bool on = false; if (on != under) m_current_run.SetUnderlined(false); } void ON_RtfFirstChar::Strikethrough(const wchar_t* value) { } #pragma endregion // RtfFirstChar #endif // RTFFIRSTCHAR ON_RtfParser::ON_RtfParser(ON_TextIterator& iter, ON_TextBuilder& builder) : m_ti(iter) , m_builder(builder) { } ON_Color ParseColor(const wchar_t* value) { ON_Color color; return color; } bool ON_RtfParser::ProcessTag(const wchar_t* name, const wchar_t* value, bool optional) { ON_wString tagname(name); if( tagname.IsEmpty() ) return false; bool rc = true; const bool bOrdinalIgnoreCase = true; if (0 == tagname.CompareOrdinal(tagRtf, bOrdinalIgnoreCase)) { m_in_real_rtf = true; m_builder.BeginHeader(); } else if (0 == tagname.CompareOrdinal(tagFontTable, bOrdinalIgnoreCase)) m_builder.BeginFontTable(); else if (0 == tagname.CompareOrdinal(tagDefaultFont, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) m_builder.DefaultFont(value); else if (0 == tagname.CompareOrdinal(tagFont, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) m_builder.FontTag(value); else if (0 == tagname.CompareOrdinal(tagFontSize, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) m_builder.FontSize(value); else if (0 == tagname.CompareOrdinal(tagCharSet, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) m_builder.CharSet(value); else if (0 == tagname.CompareOrdinal(tagCodePage, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) m_builder.CodePage(value); else if (0 == tagname.CompareOrdinal(tagNewline, bOrdinalIgnoreCase)) m_builder.Newline(); else if (0 == tagname.CompareOrdinal(tagParagraph, bOrdinalIgnoreCase)) m_builder.Paragraph(); else if (0 == tagname.CompareOrdinal(tagParagraphDefaults, bOrdinalIgnoreCase)) m_builder.ParagraphDefaults(); else if (0 == tagname.CompareOrdinal(tagSection, bOrdinalIgnoreCase)) m_builder.Section(); else if (0 == tagname.CompareOrdinal(tagTabulator, bOrdinalIgnoreCase)) m_builder.Tab(); else if (0 == tagname.CompareOrdinal(tagBold, bOrdinalIgnoreCase)) m_builder.Bold(value); else if (0 == tagname.CompareOrdinal(tagItalic, bOrdinalIgnoreCase)) m_builder.Italic(value); else if (0 == tagname.CompareOrdinal(tagUnderLine, bOrdinalIgnoreCase)) { if ((*value) == L'0') m_builder.UnderlineOff(); else m_builder.UnderlineOn(); } else if (0 == tagname.CompareOrdinal(tagUnderLineNone, bOrdinalIgnoreCase)) m_builder.UnderlineOff(); else if (0 == tagname.CompareOrdinal(tagStrikeThrough, bOrdinalIgnoreCase)) m_builder.Strikethrough(value); else if (0 == tagname.CompareOrdinal(tagSuperscript, bOrdinalIgnoreCase)) m_builder.Superscript(); else if (0 == tagname.CompareOrdinal(tagSubscript, bOrdinalIgnoreCase)) m_builder.Subscript(); else if (0 == tagname.CompareOrdinal(tagNoSuperSub, bOrdinalIgnoreCase)) m_builder.NoSuperSub(); else if (0 == tagname.CompareOrdinal(tagColorTable, bOrdinalIgnoreCase)) m_builder.BeginColorTable(); else if (0 == tagname.CompareOrdinal(tagColorRed, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) m_builder.ColorRed(value); else if (0 == tagname.CompareOrdinal(tagColorGreen, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) m_builder.ColorGreen(value); else if (0 == tagname.CompareOrdinal(tagColorBlue, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) m_builder.ColorBlue(value); else if (0 == tagname.CompareOrdinal(tagColorForeground, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) m_builder.ColorForeground(value); else if (0 == tagname.CompareOrdinal(tagColorBackground, bOrdinalIgnoreCase) && 0 != value && 0 != value[0]) m_builder.ColorBackground(value); else if (0 == tagname.CompareOrdinal(tagStackFraction, bOrdinalIgnoreCase)) m_builder.SetStackScale(value); else if (0 == tagname.CompareOrdinal(tagStackText, bOrdinalIgnoreCase)) m_builder.StackFraction(value); else if (0 == tagname.CompareOrdinal(tagStackEnd, bOrdinalIgnoreCase)) m_builder.StackEnd(); else if (0 == ON_wString::CompareOrdinal(name, tagField, bOrdinalIgnoreCase)) m_builder.TextField(value); else if (0 == ON_wString::CompareOrdinal(name, tagUniCpCount, bOrdinalIgnoreCase)) m_builder.UniCpCount(value); else if (0 == ON_wString::CompareOrdinal(name, tagUniCharDec, bOrdinalIgnoreCase)) m_builder.UniDecimal(value); else if (0 == ON_wString::CompareOrdinal(name, tagUniTwoDest, bOrdinalIgnoreCase)) m_builder.UniEmbeddedDest(value); else if (0 == ON_wString::CompareOrdinal(name, tagUniDest, bOrdinalIgnoreCase)) m_builder.UniDest(value); else if (0 == ON_wString::CompareOrdinal(name, taglquote, bOrdinalIgnoreCase)) m_builder.LQuote(); else if (0 == ON_wString::CompareOrdinal(name, tagrquote, bOrdinalIgnoreCase)) m_builder.RQuote(); else if (0 == ON_wString::CompareOrdinal(name, tagldblquote, bOrdinalIgnoreCase)) m_builder.LDblQuote(); else if (0 == ON_wString::CompareOrdinal(name, tagrdblquote, bOrdinalIgnoreCase)) m_builder.RDblQuote(); else if (0 == ON_wString::CompareOrdinal(name, tagbullet, bOrdinalIgnoreCase)) m_builder.Bullet(); else if (0 == ON_wString::CompareOrdinal(name, tagendash, bOrdinalIgnoreCase)) m_builder.EnDash(); else if (0 == ON_wString::CompareOrdinal(name, tagemdash, bOrdinalIgnoreCase)) m_builder.EmDash(); else rc = false; return rc; } // Called after '\\*' is read // '\\tagname ... ;' follows optional tag. // optional tags can be skipped, and terminate with ';' // the first tagname following '\\*\\' is the name of the optional tag and // additional tags and tokens can follow, up to a terminiating ';' // tags can contain up to 32 chars in the name optionally followed by a number bool ON_RtfParser::ReadOptionalTag() { bool rc = false; bool end_of_tag = false; ON__UINT32 cp = 0; while (!end_of_tag) { if (false == m_ti.ReadCodePoint(cp)) { end_of_tag = true; break; } switch (cp) { case ON_UnicodeCodePoint::ON_Backslash: rc = ReadTag(true); if (!rc && m_suspend_to_close == 0) m_suspend_to_close = 1; break; default: // terminates an optional tag end_of_tag = true; // Terminating chars other than these are eaten here if ('\\' == cp || '{' == cp || '}' == cp) m_ti.Back(); break; } } return rc; } // Called after '\\' is read // tags can contain up to 32 chars in the name optionally followed by a number bool ON_RtfParser::ReadTag(bool optional) { wchar_t name[64]; // These are limited to 32 chars in the rtf spec wchar_t value[64]; memset(name, 0, 64*sizeof(wchar_t)); memset(value, 0, 64*sizeof(wchar_t)); int namecnt = 0, valcnt = 0; bool in_name = true; bool end_of_tag = false; ON__UINT32 cp = 0; bool rc = false; while(!end_of_tag) { if( false == m_ti.ReadCodePoint(cp) ) { end_of_tag = true; break; } if(in_name && IsAtoZ(cp) && namecnt < 63) // Tag name is all alpha chars { name[namecnt++] = (wchar_t)cp; } else if((IsDigit(cp) || '.' == cp || ('-' == cp && valcnt == 0)) && valcnt < 63) // digits following name is tag parameter { in_name = false; value[valcnt++] = (wchar_t)cp; } else // tag name terminated by any non-alphabetic char, tag parameter argument terminated by non-digit char { end_of_tag = true; if(0 != ON_wString::CompareOrdinal(name, tagUniCharDec, true)) m_builder.FormatChange(); rc = ProcessTag(name, value, optional); // Terminating chars other than these are eaten here if('\\' == cp || '{' == cp || '}' == cp) m_ti.Back(); } } return rc; } // input: cp_array is an array of codepoints // Encodes to UTF16 to make string for text in the run bool ON_RtfParser::FlushCurText(ON_SimpleArray< ON__UINT32 >& cp_array) { // Calling this with an empty array does nothing int cp32_count = cp_array.Count(); if(cp32_count <= 0) return false; m_builder.FlushText(cp32_count, cp_array.Array()); cp_array.Empty(); return true; } bool ON_RtfParser::Parse() { bool rc = true; bool end_of_string = false; bool optional_tag = false; ON__UINT32 rtf_code_point; while(!end_of_string) { if (m_suspend_to_close <= 0 && m_ti.AtBackslashTic()) { // parse the entire contiguous MBCS string up to // \`XX\`XX...\`XX // != \`, XX = hex digit 0 to FF. // This string is a Windows MBCS string with code page identified by // const ON__UINT32 char_count = Internal_ParseMBCSString( m_builder.m_current_props.CodePage() ); if (char_count > 0) continue; } if(false == m_ti.ReadCodePoint(rtf_code_point)) break; if (m_suspend_to_close > 0) { switch (rtf_code_point) { case L'}': m_suspend_to_close--; break; case L'{': m_suspend_to_close++; break; } if (m_suspend_to_close == 0) m_ti.Back(); continue; } // Found a single byte character switch (rtf_code_point) { case ON_UnicodeCodePoint::ON_Backslash: { if ( m_ti.ReadCodePoint(rtf_code_point) ) { // character following '\\' // Its a tag of some kind, or a literal char // These are special characters that follow '\\' switch (rtf_code_point) { // Literal escaped '\' or '{' or '}' // Append to current text string case ON_UnicodeCodePoint::ON_Backslash: case L'{': case L'}': m_builder.AppendCodePoint(rtf_code_point); break; // Paragraph tag when following '\\' case ON_UnicodeCodePoint::ON_LineFeed: case ON_UnicodeCodePoint::ON_CarriageReturn: case ON_UnicodeCodePoint::ON_LineSeparator: case ON_UnicodeCodePoint::ON_ParagraphSeparator: FlushCurText(m_builder.m_current_codepoints); m_builder.RunEnd(); ProcessTag(L"par", nullptr, false); m_builder.RunBegin(); break; case '\'': // This case sould never occur - it is handled by // Internal_ParseMBCSString at the beginning of this while statement. ON_ERROR("Bug in RTF parsing code."); break; // Symbol tags... case '~': // non-break space if (!m_builder.AppendCodePoint((ON__UINT32)' ')) return true; break; case '-': // optional hyphen break; case '_': // non-break hyphen if (!m_builder.AppendCodePoint((ON__UINT32)'-')) return true; break; case '|': case ':': break; case '*': ReadOptionalTag(); break; //case 'n': // //FlushCurText(m_builder.m_current_codepoints); // m_builder.GroupEnd(); // ProcessTag(L"par", nullptr, false); // m_builder.GroupBegin(); // break; default: // Tag names are always low ascii alphabetic m_ti.Back(); ReadTag(false); //// Breaks reading unocode text like A<\u26085?\u26412?\u12398?\u12469?\u12452?> //// m_builder.m_current_codepoints.Empty(); optional_tag = false; } } } break; // Character not immediately following '\\' // Skip newline characters if this is a real rtf string - just white space // If this is one of our recomposed plain text strings, \n means newline case ON_UnicodeCodePoint::ON_LineFeed: case ON_UnicodeCodePoint::ON_LineSeparator: if (!m_in_real_rtf) { FlushCurText(m_builder.m_current_codepoints); m_builder.GroupEnd(); ProcessTag(L"par", nullptr, false); m_builder.GroupBegin(); } break; case ON_UnicodeCodePoint::ON_NoBreakSpace: case ON_UnicodeCodePoint::ON_NarrowNoBreakSpace: case ON_UnicodeCodePoint::ON_ZeroWidthSpace: if (!m_builder.AppendCodePoint((ON__UINT32)' ')) return true; break; case ON_UnicodeCodePoint::ON_CarriageReturn: break; case ON_UnicodeCodePoint::ON_Tab: // '\tab' tag // ProcessTag(new RtfTag(tagTabulator)); break; // Start group case '{': //FlushCurText(m_builder.m_current_codepoints); m_builder.GroupBegin(); m_p_level++; break; case '}': //FlushCurText(m_builder.m_current_codepoints); m_builder.GroupEnd(); if (m_p_level > 0) { m_p_level--; } else { // {} underflow } break; default: // Normal single byte text character not in a tag name // Could be argument to a tag, like a font name in fonttbl // AppendCodePoint() returns false to stop parsing if (m_builder.ReadingFontDefinition() && rtf_code_point == L';') m_builder.FinishFontDef(); else { if (!m_builder.AppendCodePoint(rtf_code_point)) return true; } break; } } // end of big loop for whole string FlushCurText(m_builder.m_current_codepoints); m_builder.GroupEnd(); return rc; } // This has to make a string that the text control can digest // and that's short and simple as possible so it can be put in the file unsigned int RtfComposer::GetFacenameKey(const ON_Font* font, ON_ClassArray< ON_wString >& fonttable) { if (nullptr == font) return 0; // Depending on what created the RTF, the face name in the RTF can be a // PostScript name, LOGFONT.lfFaceName, IDWriteFont family name, ... const ON_wString rtf_facename = font->QuartetName(ON_Font::NameLocale::English); if (rtf_facename.IsEmpty()) return 0; #if defined(ON_RUNTIME_WIN) if (rtf_facename.Length() > 33) ON_ERROR("rtf_facename is longer than Windows LogfontName\n"); #endif return RtfComposer::GetFacenameKey(rtf_facename.Array(), fonttable); } unsigned int RtfComposer::GetFacenameKey(const wchar_t* facename, ON_ClassArray< ON_wString >& fonttable) { if(nullptr == facename || 0 == facename[0]) return 0; int count = fonttable.Count(); for(int i = 0; i < count; i++) { if (nullptr == fonttable[i] || 0 == fonttable[i][0]) continue; if(ON_wString::EqualOrdinal(facename, fonttable[i], true)) return i; } fonttable.AppendNew() = facename; return count; } unsigned int RtfComposer::GetColorKey(ON_Color color, ON_SimpleArray< unsigned int >& colortable) { int count = colortable.Count(); for(int i = 0; i < count; i++) { if(color == colortable[i]) return i; } colortable.Append((unsigned int)color); return count; } bool RtfComposer::FormatTextHeight(double height, ON_wString& str) { str.Format(L"\\*h%lf", height); return true; } bool IsSpecial(const wchar_t ch) { if (L'\\' == ch) return true; if (L'}' == ch) return true; if (L'{' == ch) return true; return false; } static bool GetRunText(ON_TextRun* run, ON_wString& text_out, bool& foundunicodepoint) { if (nullptr == run) return false; // Have to convert codepoints directly instead of // calling DisplayString() because of field evaluation // and because RTF uses a "signed UTF-16" encoding and converting the run // into RTF has to work on Apple platforms where wchar_t strings // are UTF-32 encoded strings. (Ask Dale Lear if you have questions). const ON__UINT32* run_code_points = run->UnicodeString(); // null terminated if (nullptr != run_code_points && 0 != run_code_points[0]) { for (int ci = 0; 0 != run_code_points[ci]; ci++) { const ON__UINT32 code_point = run_code_points[ci]; // works on Windows and Apple ON__UINT16 utf16[2] = { 0 }; const int utf16_count = ON_EncodeUTF16(code_point, utf16); if (utf16_count < 0 || utf16_count > 2 || 0 == utf16[0]) continue; if (code_point > 0x80 || 1 != utf16_count || code_point != (ON__UINT32)utf16[0]) { // When we write RTF, we do not specify what encodding is used for values in the range 0x80 - 0xFF. // // The ON_wString temp should to have UTF-16 encoding on Windows platforms // and UTF-32 encoding on Apple platforms. // // There are 27 "tricky values" in this range 0x80 - 0xFF where Windows-1252 maps the value to a glyph and // and UNICODE maps the value to a control that typically has no printable glyph. // These "tricky values" are all in the range 0x80 ... 0x9F. // An example is the Euro sign (Windows-1252 0x80 = Euro sign, UNICODE U+0080 = xxx control, // UNOCODE U+20AC = Euro sign). // // The RTF we get from Windows controls, like the "Text" command dialog box, // typically specifies it is using Windows-1252 and encodes the Euro sign as \`80. // So, if we have one of these "euro like" values, we will explicitly write it as a UNICODE value // to avoid the possiblity of something defaulting to using Windows-1252. // https://mcneel.myjetbrains.com/youtrack/issue/RH-38205 // // See ON_DecodeWindowsCodePage1252Value() for more details. // // UNOCODE code points that require UTF-16 surrogate pair encodings have // two RTF values TWO \uN?\uN? values. // For example, UNOCODE code point U+1F5D1 has UTF-16 encodeing (0xD83D, 0xDDD1) // and the RTF looks like ...{\ltrch \u-10179?\u-8751?}. for (int utf16i = 0; utf16i < utf16_count; utf16i++) { ON_wString n; const ON__INT16 signed_rtf_utf16_value = (ON__INT16)utf16[utf16i]; // will be negative when utf16[utf16i] >= 0x8000; const int signed_string_format_param = (int)signed_rtf_utf16_value; n.Format(L"\\u%d?", signed_string_format_param); text_out += n; foundunicodepoint = true; } } else { // code_point < 0x80 (ASCII value range) and casting to wchar_t will work // on any platform; wchar_t ascii_value = (wchar_t)code_point; if (IsSpecial(ascii_value)) { text_out += L'\\'; } text_out += ascii_value; } } } return true; } bool RtfComposer::Compose( const ON_TextContent* text, ON_wString& rtf, bool bForceRtf) { if (0 == text) return false; if (!RtfComposer::RecomposeRTF()) { rtf = text->RtfText(); return true; } ON_TextRunArray* runs = text->TextRuns(true); if (nullptr == runs) return false; const ON_Font& style_font = text->DefaultFont(); const ON_wString style_fontname = style_font.RichTextFontName(); if (style_fontname.IsEmpty()) return false; bool style_bold = style_font.IsBoldInQuartet(); bool style_italic = (ON_Font::Style::Italic == style_font.FontStyle()); bool style_underline = style_font.IsUnderlined(); bool style_strikeout = style_font.IsStrikethrough(); bool chg_bold = false; bool chg_italic = false; bool chg_underline = false; bool chg_strikeout = false; bool chg_facename = false; // First facename is from the ON_TextContent (style_font) // Any after that are from runs ON_ClassArray< ON_wString > fonttable(8); // Creates a fonttable entry if the font isn't in the fonttable // Returns the table index if it is unsigned int stylefont_key = GetFacenameKey(style_fontname, fonttable); int runcount = runs->Count(); int nlcount = 0; // See if this is multi-line text bool multiline = false; for (int ri = 0; ri < runcount; ri++) { ON_TextRun* run = (*runs)[ri]; if (nullptr != run) { if (ON_TextRun::RunType::kText == run->Type() && 0 < nlcount) { multiline = true; break; } else if (ON_TextRun::RunType::kNewline == run->Type() || ON_TextRun::RunType::kParagraph == run->Type()) nlcount++; } } bool make_rtf = bForceRtf; ON_SimpleArray< ON_TextRun* > runholders; for (int ri = 0; ri < runcount; ri++) { ON_TextRun* run = (*runs)[ri]; if (nullptr != run) { if ( ON_TextRun::RunType::kText == run->Type() || ON_TextRun::RunType::kField == run->Type() ) { const ON_Font* run_font = run->Font(); if (nullptr != run_font) { runholders.AppendNew() = run; if (!chg_bold) chg_bold = run_font->IsBoldInQuartet() != style_bold; if (!chg_italic) chg_italic = run_font->IsItalic() != style_italic; if (!chg_underline) chg_underline = run_font->IsUnderlined() != style_underline; if (!chg_strikeout) chg_strikeout = run_font->IsStrikethrough() != style_strikeout; const ON_wString run_fontname = run_font->RichTextFontName(); if (run_fontname.IsEmpty()) return false; unsigned int run_font_key = GetFacenameKey(run_fontname, fonttable); if (!chg_facename) chg_facename = run_font_key != stylefont_key; } const ON__UINT32* run_codepoints = run->UnicodeString(); if (!make_rtf && nullptr != run_codepoints) { for (int i = 0; run_codepoints[i] != 0; i++) { if (run_codepoints[i] > 127 || run_codepoints[i] == '\\' || run_codepoints[i] == '{' || run_codepoints[i] == '}' ) { make_rtf = true; break; } } } } else if ( ON_TextRun::RunType::kParagraph == run->Type() || ON_TextRun::RunType::kNewline == run->Type() ) { runholders.AppendNew() = run; const ON_Font* run_font = run->Font(); if (nullptr != run_font) { const ON_wString run_fontname = run_font->RichTextFontName(); if (!run_fontname.IsEmpty()) { unsigned int run_font_key = GetFacenameKey(run_fontname, fonttable); if (!chg_facename) chg_facename = run_font_key != stylefont_key; } } } } } // end of getting runinfo if (chg_bold || chg_italic || chg_underline || chg_strikeout || chg_facename) make_rtf = true; ON_wString run_strings; ON_wString temp; runcount = runholders.Count(); for (int ri = 0; ri < runcount; ri++) { ON_TextRun* run = runholders[ri]; if (nullptr == run) continue; if ( ON_TextRun::RunType::kText == run->Type() || ON_TextRun::RunType::kField == run->Type() ) { if (make_rtf || (run->IsStacked() == ON_TextRun::Stacked::kStacked && run->m_stacked_text != 0)) { unsigned int run_font_key = ON_UNSET_UINT_INDEX; if (make_rtf) { const ON_Font* run_font = run->Font(); if (nullptr == run_font) continue; // add properties for this string run_strings += L"{"; bool addspace = false; run_font_key = GetFacenameKey(run_font, fonttable); temp.Format(L"\\f%d", run_font_key); run_strings += temp; addspace = true; if (run_font->IsBoldInQuartet()) { run_strings += L"\\b"; addspace = true; } else if (style_bold) { run_strings += L"\\b0"; addspace = true; } if (run_font->IsItalic()) { run_strings += L"\\i"; addspace = true; } else if (style_italic) { run_strings += L"\\i0"; addspace = true; } if (run_font->IsUnderlined()) { run_strings += L"\\ul"; addspace = true; } else if (style_underline) { run_strings += L"\\ul0"; addspace = true; } if (run->IsStacked() == ON_TextRun::Stacked::kBottom) { run_strings += L"\\sub"; addspace = true; } if (run->IsStacked() == ON_TextRun::Stacked::kTop) { run_strings += L"\\super"; addspace = true; } if (addspace) run_strings += L" "; } if (ON_TextRun::RunType::kField != run->Type() && run->IsStacked() == ON_TextRun::Stacked::kStacked && run->m_stacked_text != 0) { // RH-64720 - Only do this if the stack isn't from a field run_strings += L"[["; GetRunText(run->m_stacked_text->m_top_run, run_strings, make_rtf); run_strings += run->m_stacked_text->m_separator; GetRunText(run->m_stacked_text->m_bottom_run, run_strings, make_rtf); run_strings += L"]]"; } else if (ON_TextRun::RunType::kField == run->Type()) { //run_strings += L"%<"; //GetRunText(run, run_strings, make_rtf); //run_strings += L">%"; } else { GetRunText(run, run_strings, make_rtf); } if (ri < runcount - 1) { ON_TextRun* par_run = runholders[ri+1]; if (nullptr != par_run && (ON_TextRun::RunType::kNewline == par_run->Type() || ON_TextRun::RunType::kParagraph == par_run->Type())) { const ON_Font* par_run_font = par_run->Font(); if (nullptr != par_run_font) { unsigned int par_run_font_key = GetFacenameKey(par_run_font, fonttable); if (par_run_font_key == run_font_key) { run_strings += L"\\par"; ri++; } } } } if (make_rtf) run_strings += L"}"; } else // not making rtf, just collect text from runs { GetRunText(run, run_strings, make_rtf); } } else if (ON_TextRun::RunType::kNewline == run->Type() || ON_TextRun::RunType::kParagraph == run->Type()) { if (make_rtf) { temp = L"{\\par}"; const ON_Font* par_run_font = run->Font(); if (nullptr != par_run_font) { unsigned int par_run_font_key = GetFacenameKey(par_run_font, fonttable); if(par_run_font_key != stylefont_key) temp.Format(L"{\\f%d \\par}", par_run_font_key); } run_strings += temp; } else if(ri < runcount - 1 && multiline) // not making rtf run_strings += L"\n"; } } // end of collecting run text int nfont = fonttable.Count(); // Any time the font for the annotation's style has // bold, italic or underline set, we have to write rtf // even if there are no changes in the string itself if (run_strings.Length() > 0 && make_rtf) { // deff0 means use font0 for the default font throughout the string. // If we include font0 in the font table, when we send // the string back to the RTF control, the font listed as // font0 will be used instead of the default font for the // style. // So the default font is listed as 0 and there is no // entry in the table for font0. rtf.Format(L"{\\rtf1"); if (0 < nfont) { ON_wString fonttable_string; temp.Format(L"\\deff%d", stylefont_key); rtf += temp; fonttable_string = L"{\\fonttbl"; for (int fi = 0; fi < nfont; fi++) { // temp.Format(L"{\\f%d %s;}", fi, fonttable[fi].Array()); fonttable_string += temp; } rtf += fonttable_string; } if (!RtfComposer::ComposeFS()) temp.Format(L"}\\f%d", stylefont_key); else temp.Format(L"}\\f%d \\fs%d", stylefont_key, RtfComposer::TextEditorFontSize()); rtf += temp; if (style_bold) rtf += L"\\b"; if (style_italic) rtf += L"\\i"; if (style_underline) rtf += L"\\ul"; rtf += run_strings; // backing out the change made for RH-49725 because it causes the problem reported in RH-50733 rtf += L"}"; } else { rtf = run_strings; } return true; } // Turns on or off composing for debugging bool RtfComposer::m_bComposeRTF = true; bool RtfComposer::RecomposeRTF() { return RtfComposer::m_bComposeRTF; } void RtfComposer::SetRecomposeRTF(bool b) { RtfComposer::m_bComposeRTF = b; } // Turns on or off adding \FS40 to composed strings bool RtfComposer::m_bComposeFS = true; bool RtfComposer::ComposeFS() { return RtfComposer::m_bComposeFS; } void RtfComposer::SetComposeFS(bool b) { RtfComposer::m_bComposeFS = b; } int RtfComposer::m_TextEditorSize = 0; int RtfComposer::TextEditorFontSize() { if (0 < RtfComposer::m_TextEditorSize) return RtfComposer::m_TextEditorSize; else return 18; } void RtfComposer::SetTextEditorFontSize(unsigned int size) { RtfComposer::m_TextEditorSize = size; } static const ON_wString Internal_PostScriptNameIfAvailable(const ON_Font& managed_font) { ON_wString style_fontname = managed_font.PostScriptName(); if (style_fontname.IsNotEmpty()) return style_fontname; return ON_wString::FormatToString(L"Postscript-name-not-available-%u", managed_font.ManagedFontSerialNumber()); } const ON_wString RtfComposer::ComposeAppleRTF( const ON_TextContent* text) { ON_wString rtf_string; if (0 == text) return rtf_string; ON_TextRunArray* runs = text->TextRuns(true); if (nullptr == runs) return rtf_string; const ON_Font& style_font = text->DefaultFont(); const ON_wString style_fontname = Internal_PostScriptNameIfAvailable(style_font); // First facename is from the ON_TextContent // Any after that are from runs ON_ClassArray< ON_wString > fonttable(8); // Creates a fonttable entry the first time unsigned int deffont_key = GetFacenameKey(style_fontname, fonttable); int runcount = runs->Count(); int nlcount = 0; // See if this is multi-line text bool multiline = false; for (int ri = 0; ri < runcount; ri++) { ON_TextRun* run = (*runs)[ri]; if (nullptr != run) { if (ON_TextRun::RunType::kText == run->Type() && 0 < nlcount) multiline = true; else if (ON_TextRun::RunType::kNewline == run->Type() || ON_TextRun::RunType::kParagraph == run->Type()) nlcount++; } } ON_SimpleArray< ON_TextRun* > runholders; for (int ri = 0; ri < runcount; ri++) { ON_TextRun* run = (*runs)[ri]; if (nullptr != run) { if ( ON_TextRun::RunType::kText == run->Type() || ON_TextRun::RunType::kField == run->Type() ) { const ON_Font* run_font = run->Font(); if (nullptr != run_font) { runholders.AppendNew() = run; } } else if ( ON_TextRun::RunType::kParagraph == run->Type() || ON_TextRun::RunType::kNewline == run->Type() ) { runholders.AppendNew() = run; } } } // end of getting runinfo ON_wString run_strings; ON_wString temp; runcount = runholders.Count(); for (int ri = 0; ri < runcount; ri++) { ON_TextRun* run = runholders[ri]; if (nullptr == run) continue; if ( ON_TextRun::RunType::kText == run->Type() || ON_TextRun::RunType::kField == run->Type() ) { const ON_Font* run_font = run->Font(); if (nullptr == run_font) continue; const ON_wString& run_fontname = Internal_PostScriptNameIfAvailable(*run_font); // add properties for this string run_strings += L"{"; bool addspace = false; unsigned int run_font_key = GetFacenameKey(run_fontname, fonttable); temp.Format(L"\\f%d", run_font_key); run_strings += temp; addspace = true; if (run_font->IsBoldInQuartet()) { run_strings += L"\\b"; addspace = true; } if (run_font->IsItalic()) { run_strings += L"\\i"; addspace = true; } if (run_font->IsUnderlined()) { run_strings += L"\\ul"; addspace = true; } if (addspace) run_strings += L" "; bool true_bool = true; if (run->IsStacked() == ON_TextRun::Stacked::kStacked && run->m_stacked_text != 0) { run_strings += L"[["; GetRunText(run->m_stacked_text->m_top_run, run_strings, true_bool); run_strings += run->m_stacked_text->m_separator; GetRunText(run->m_stacked_text->m_bottom_run, run_strings, true_bool); run_strings += L"]]"; } else if (ON_TextRun::RunType::kField == run->Type()) { run_strings += L"%<"; GetRunText(run, run_strings, true_bool); run_strings += L">%"; } else { GetRunText(run, run_strings, true_bool); } if (ri < runcount - 2) { ON_TextRun* par_run = runholders[ri + 1]; if (nullptr != par_run && (ON_TextRun::RunType::kNewline == par_run->Type() || ON_TextRun::RunType::kParagraph == par_run->Type())) { const ON_Font* par_run_font = par_run->Font(); if (nullptr != par_run_font) { const ON_wString& par_run_fontname = Internal_PostScriptNameIfAvailable(*par_run_font); if (!par_run_fontname.IsEmpty()) { unsigned int par_run_font_key = GetFacenameKey(par_run_fontname, fonttable); if (par_run_font_key == run_font_key) { run_strings += L"\\\n"; ri++; } } } } } run_strings += L"}"; } else if (ri < runcount - 1 && multiline && (ON_TextRun::RunType::kNewline == run->Type() || ON_TextRun::RunType::kParagraph == run->Type())) { temp = L"{\\par}"; const ON_Font* run_font = run->Font(); if (nullptr != run_font) { const ON_wString run_fontname = Internal_PostScriptNameIfAvailable(*run_font); unsigned int run_font_key = GetFacenameKey(run_fontname, fonttable); if(run_font_key != deffont_key) temp.Format(L"{\\f%d \\par}", run_font_key); } run_strings += temp; } } // end of collecting run text int nfont = fonttable.Count(); if (run_strings.Length() > 0) { rtf_string.Format(L"{\\rtf1"); if (0 < nfont) { ON_wString fonttable_string; temp.Format(L"\\deff%d", deffont_key); rtf_string += temp; fonttable_string = L"{\\fonttbl"; for (int fi = 0; fi < nfont; fi++) { temp.Format(L"{\\f%d %s;}", fi, fonttable[fi].Array()); fonttable_string += temp; } rtf_string += fonttable_string; } if (!RtfComposer::ComposeFS()) temp.Format(L"}\\f%d", deffont_key); else temp.Format(L"}\\f%d \\fs%d", deffont_key, RtfComposer::TextEditorFontSize()); rtf_string += temp; rtf_string += run_strings; rtf_string += L"}"; } return rtf_string; }