From 5dc66c5b4e91365d7acab20f0db2bb0288b030d6 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sun, 14 Jun 2026 22:56:23 +0200 Subject: [PATCH] fixed: roundtrip --- AppKit/CPTextView/_CPRTFParser.j | 50 +++++++++++++++---------- Tests/Manual/CPTextView/AppController.j | 42 ++++++++++++++++++++- 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/AppKit/CPTextView/_CPRTFParser.j b/AppKit/CPTextView/_CPRTFParser.j index 17000ca7e..00bd18053 100644 --- a/AppKit/CPTextView/_CPRTFParser.j +++ b/AppKit/CPTextView/_CPRTFParser.j @@ -36,6 +36,7 @@ @global CPBackgroundColorAttributeName @global CPParagraphStyleAttributeName @global CPAttachmentAttributeName +@global CPUnderlineStyleAttributeName @global CPLeftTabStopType @global CPRightTabStopType @@ -210,7 +211,6 @@ var cp1252Map = { return ret; } - @end @@ -348,6 +348,9 @@ var kRgsymRtf = { _tableRows = nil; _currentRow = nil; _currentCellText = ""; + + // Safe Initialization + _currentRun = [_RTFAttribute new]; } return self; @@ -390,6 +393,13 @@ var kRgsymRtf = { [self _flushCurrentRun]; _currentRun = state.run; + + // Safety guard to prevent setting properties on null + if (!_currentRun) + { + _currentRun = [_RTFAttribute new]; + } + _currentRun._range = CPMakeRange([_result length], 0); if (_curState == 0) @@ -397,7 +407,6 @@ var kRgsymRtf = { _parsingFontTable = NO; } } - return YES; } @@ -609,6 +618,23 @@ var kRgsymRtf = { break; + case "ul": // underline + if (param === 0) + { + if (_currentRun && _currentRun.underline) + [self _flushCurrentRun]; + + _currentRun.underline = NO; + } + else + { + if (_currentRun && !_currentRun.underline) + [self _flushCurrentRun]; + + _currentRun.underline = YES; + } + break; + case "qc": // paragraph center [_currentRun.paragraph setAlignment:CPCenterTextAlignment]; break; @@ -632,23 +658,6 @@ var kRgsymRtf = { case "paperh": _paper.height = param; break; - - case "ul": // underline - if (param === 0) - { - if (_currentRun && _currentRun.underline) - [self _flushCurrentRun]; - - _currentRun.underline = NO; - } - else - { - if (_currentRun && !_currentRun.underline) - [self _flushCurrentRun]; - - _currentRun.underline = YES; - } - break; } return ''; @@ -1033,7 +1042,8 @@ var kRgsymRtf = { _freename += tmp; } } - break; + + break; } } diff --git a/Tests/Manual/CPTextView/AppController.j b/Tests/Manual/CPTextView/AppController.j index f35d0730c..a34d50663 100755 --- a/Tests/Manual/CPTextView/AppController.j +++ b/Tests/Manual/CPTextView/AppController.j @@ -154,7 +154,7 @@ var rtfButton = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 150, 30)]; [rtfButton setTitle:@"RTF Round-trip ➔"]; [rtfButton setTarget:self]; - [rtfButton setAction:@selector(makeRTF:)]; + [rtfButton setAction:@selector(rtfRoundTrip:)]; [toolbarView addSubview:rtfButton]; currentX += 160; @@ -366,4 +366,44 @@ [_textView insertText: mystr]; } +// Action tied to the "RTF Round-trip ->" button in the demo app +- (void)rtfRoundTrip:(id)sender +{ + // 1. Retrieve the rich text storage from the editor on the left + var textStorage = [_textView textStorage]; + if (!textStorage || [textStorage length] == 0) + { + return; + } + + // 2. Serialize the CPAttributedString into an RTF string + var docAttributes = @{ @"PaperSize": CPMakeSize(612, 792) }; + var generatedRTF = [_CPRTFProducer produceRTF:textStorage documentAttributes:docAttributes]; + + // 3. Set the generated RTF string into the raw output pane on the right + [_textView2 setString:generatedRTF]; + + // 4. Parse that exact RTF text back into a new CPAttributedString + var parser = [[_CPRTFParser alloc] init]; + var roundTrippedString = [parser parseRTF:generatedRTF]; + + // 5. Replace the editor's text storage with the round-tripped version + var editorStorage = [_textView textStorage]; + if (editorStorage && [editorStorage respondsToSelector:@selector(setAttributedString:)]) + { + [editorStorage setAttributedString:roundTrippedString]; + } + else + { + // Safe fallback sequence + [_textView setEditable:YES]; + [_textView setString:@""]; + [_textView insertText:roundTrippedString]; + [_textView setEditable:NO]; + } + + // 6. Force a layout pass and render update + [_textView setNeedsDisplay:YES]; + [_textView2 setNeedsDisplay:YES]; +} @end