mirror of
https://github.com/mcneel/opennurbs.git
synced 2026-03-01 11:36:09 +08:00
3682 lines
102 KiB
C++
3682 lines
102 KiB
C++
//
|
|
// 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 <http://www.opennurbs.org>.
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
#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<const char*>(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<Fraction>& 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<Fraction> 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<value> ... ;' 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 <OTHER>
|
|
// \`XX\`XX...\`XX<OTHER>
|
|
// <OTHER> != \`, 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;
|
|
}
|