RTF text debugging

Hi Rhino team!

I’ve been using Claude to write a Rhino plugin that’s specific to my line of work: a Gantt chart / calendar scheduler plugin for the film industry.

It’s been going pretty well but I’ve hit a brick wall with a feature that it can’t seem to solve.

I wonder if someone in your team could help? If so, the summary is below. I’d appreciate any help!

I have some scheduler notes in different categories. One category, Shoot Days, which is a fairly new feature, I’ve implemented Real Text Functions so I can Bold, Italic, Underline per character if I want.

Once that was implemented I asked Claude to implement RFT on an older feature, the Task Notes. For the life of it and me, it can’t get it to work after many sessions over many days. The latest message back was:

----------------------------

I completely understand. I’m sorry — this has been genuinely awful and I’ve failed to fix it despite many attempts.

I don’t have a supervisor, but I do want to be transparent about what I think is actually happening and give you something concrete you can take to someone else if needed.

The evidence:

  • The diagnostic showed task notes produce RTF with \b(bold marker present ✓)
  • The rendering code (DrawLineAtte.RichText) is identical to shoot days
  • Shoot day bold works, task note bold doesn’t
  • We’ve tried every variation of RTF format

My honest assessment:
The problem is almost certainly that task notes in the calendar go through DrawTextML’s non-centered path, while shoot day notes go through the centered path (DrawCenteredLineContent). Even though both call DrawLineAt, there may be something about the Rhino block geometry context — the Z position, the layer, or the display order — that differs between the two paths and causes te.RichText bold to be invisible for task notes specifically.

What would actually solve this: someone who can attach a debugger to Rhino and inspect the actual TextEntityproperties at render time. That’s beyond what I can do through code generation.

In the meantime: I can write a clean summary document of exactly where this stands, with the relevant code sections, so you can hand it to another developer. Would that help?

-----------------------------------------

Got any tips on how I can move forward?

Hi @Andrew,

The short answer is that you almost certainly don’t have a Rhino bug, and you don’t need a debugger — you need to stop hand-writing RTF.

When you assign a string to TextEntity.RichText, Rhino parses it against that entity’s dimension style. The \b in your string isn’t a formatting instruction on its own — it’s a request to switch to the bold face of whatever font the style resolves to. So \b does nothing, silently, if either of these is true:

  • The entity has no ParentDimensionStyle set at the time you assign RichText.
  • The style’s font family has no bold face available (this catches people out with a lot of display and condensed fonts).

That’s enough to explain two code paths that look identical but behave differently — it has nothing to do with centered vs. non-centered drawing, Z position, layers, or draw order.

What I’d do instead of generating RTF yourself:

var style = doc.DimStyles.Current;   // or your own style
var te = TextEntity.Create("Some text", plane, style, false, 0.0, 0.0);

Let the style carry the font, and check style.Font.Bold the family actually has the faces you want. If you genuinely need mixed formatting inside one line, use TextEntity.CreateWithRichText(rtf, plane, style, …) so the style is bound at construction time.

One more thing worth checking in the older code path: assigning te.Font = … sets the base font for the whole entity. If that happens after you’ve set RichText, it will flatten your per-run formatting.

If none of that sorts it out, post a small standalone sample — the few lines that build the entity for a task note, and the equivalent for a shoot day — and I’ll take a look at the difference.

— Dale

Hi Dale, THANK YOU SO MUCH for your response!
I fed that back to Claude but it still can’t get it to work. Below is some sample code as well as screenshots of the shoot days that are working and the task notes that aren’t. (FYI I described you as a Rhino Guru to Claude) :slight_smile:

Thanks again for any tips you might have! It’s nice to communicate with RAI (Real Actual Intelligence) for a change!

// ═══════════════════════════════════════════════════════

// SHOOT DAY NOTES — works correctly in charts
// ═══════════════════════════════════════════════════════

// 1. RTF extracted in EditShootDayDialog.Apply()
//    _editor is a RichTextArea attached to a visible dialog window
var lines = RtfUtil.ExtractPerLineRtf(_editor);
// e.g. lines[0] = "{\rtf1\ansi\ansicpg1252\uc1...{\b bold word}\cf0  normal word\par}"

// 2. Stored in ShootDayNote
assignment.DayNotes[isoDate] = new ShootDayNote { Lines = lines };

// 3. Turned into NoteLineContent in ShootHelper.BuildProjectSegment()
foreach (var l in lines)
    noteLines.Add(new NoteLineContent { Content = l });
item.NoteLines = noteLines;

// 4. Rendered via DrawTextML(centerContent: true) → DrawCenteredLineContent → DrawLineAt
DrawLineAt(nlc.Content, startX, y, heightMm, col);

// ═══════════════════════════════════════════════════════
// TASK NOTES — RTF not rendering in charts
// ═══════════════════════════════════════════════════════

// 1. RTF extracted in TaskEditDialog.ApplyToTask()
//    _notesBox is ALSO a RichTextArea attached to a visible dialog window
var taskLines = RtfUtil.ExtractPerLineRtf(_notesBox);
// taskLines[0] = "{\rtf1\ansi\ansicpg1252\uc1...{\b bold word}\cf0  normal word\par}"
// (visually identical format to shoot day output)

// 2. Stored on SchedulerTask
task.NoteLines = taskLines.Select(l => new NoteLineContent { Content = l }).ToList();

// 3. Rendered via DrawTextML(centerContent: false) — non-centered note loop
var nlc = noteLineContents[li - 1];         // nlc.Content = the same RTF string
DrawLineAt(nlc.Content, x, lineY, height, col);  // IDENTICAL DrawLineAt call

// ═══════════════════════════════════════════════════════
// DrawLineAt — shared by BOTH paths, unchanged
// ═══════════════════════════════════════════════════════
void DrawLineAt(string content, double xPos, double y, double heightMm, Color col)
{
    if (content.TrimStart().StartsWith("{\rtf"))
    {
        var te = new TextEntity {
            RichText   = content,      // same RTF format for both
            TextHeight = heightMm,     // same height for both
            Plane      = new Plane(new Point3d(xPos, y, 0), Vector3d.ZAxis)
        };
        gl.Geo.Add(te);
        gl.Col.Add(col);
    }
}

Key question for the guru: both paths call the identical DrawLineAt with RTF that appears to be the same format. Shoot day notes render bold; task notes don’t. The only structural difference is centerContent: true vs false in DrawTextML. Is there anything about the Rhino block geometry context, the order of Geo.Add calls, or how gl is assembled that could cause TextEntity.RichText bold to render on one but not the other?

Shoot Days: Working

Task Notes: Not working

Hi @Andrew,

The identical DrawLineAt is the answer, not the mystery: if the same function renders one string bold and the other not, the difference is in the string, not the draw path. Centered vs. non-centered, Geo.Add order, Z position, layers — none of that touches RTF parsing.

Log what actually reaches DrawLineAt, not what leaves the dialog:

RhinoApp.WriteLine("IN >>> " + content.Replace("\\", "\\\\"));
var te = new TextEntity { RichText = content, ... };
RhinoApp.WriteLine("OUT<<< " + te.RichText.Replace("\\", "\\\\"));

Do it for one shoot day and one task note, then diff. Two likely outcomes:

  • The IN lines differ. Shoot day notes stay in memory; task notes go onto SchedulerTask, which is presumably persisted. A serializer that escapes backslashes turns \b into \\b and you get exactly this — text renders, formatting doesn’t.
  • IN has \b, OUT doesn’t. Rhino parsed it and dropped it. \b means “switch to the bold face of the current font” — if the family in the RTF font table has no bold face, it’s silently ignored. Your two RichTextArea controls may emit different {\fonttbl} entries. Compare them.

Quick isolation, no debugger: feed the logged task-note string into the shoot-day path. If it fails there too, it’s the string.

Also, new TextEntity { ... } has no dimension style bound when you assign RichText. It works for shoot days, but it’s fragile — prefer TextEntity.CreateWithRichText(content, plane, doc.DimStyles.Current, false, 0.0, 0.0).

— Dale

Thanks so much Dale!

Unfortunately, after much confidence in your advice Claude has continued to not fix the bug after many, many attempts. The latest summary is below. Any tips??


Summary of what’s been done since the guru’s input

What works:

  • Shoot day notes: bold/italic render correctly in charts via DrawLineAtTextEntity.CreateWithRichText(rtf, plane, doc.DimStyles.Current, ...)
  • Task title bold/italic: works via RtfHelper.Styled RTF built at render time
  • SAVE RTF log confirms _notesBox.Rtf correctly captures {\b\ltrch BOLD} with Arial font table

What’s broken:

  • Task notes: NoteLines=NULL for every single task when charts are drawn
  • The RTF is captured correctly at save time but doesn’t survive the JSON round-trip

Root cause identified via diagnostics:
SchedulerTask.NoteLines had no [JsonProperty] attribute. Every other field uses explicit snake_case keys like [JsonProperty("title")]. Without the attribute, Newtonsoft.Json appears to drop the field during the save/load cycle through RhinoDoc.Strings. Added [JsonProperty("note_lines")] — but the user reports it still shows NoteLines=NULL after this fix.

Current architecture:

  • Save path: _notesBox.RtfForceArialFont() → stored in NoteLineContent.Content → serialized via DataStore.Save(_data)JsonConvert.SerializeObjectdoc.Strings.SetString("scheduler.data", json)
  • Load path: doc.Strings.GetValue("scheduler.data")JsonConvert.DeserializeObject<SchedulerData>item.NoteLines[0].Content passed to DrawLineAtCreateWithRichText

Question for the guru: Given that [JsonProperty("note_lines")] didn’t fix the NULL, what else in the Newtonsoft.Json configuration could cause a List<NoteLineContent>? property to consistently deserialize as null? Could it be that NoteLineContent (which uses public fields, not properties) needs special handling?