From 4aaa32833ab7440bbfb34ef69845d655af63e13d Mon Sep 17 00:00:00 2001 From: daboe01 Date: Mon, 15 Jun 2026 22:33:03 +0200 Subject: [PATCH] fixed: table roundtrip --- AppKit/CPTextView/_CPRTFParser.j | 100 +++--------------------- Tests/Manual/CPTextView/AppController.j | 6 -- 2 files changed, 9 insertions(+), 97 deletions(-) diff --git a/AppKit/CPTextView/_CPRTFParser.j b/AppKit/CPTextView/_CPRTFParser.j index a68d7af5f..d8f95f027 100644 --- a/AppKit/CPTextView/_CPRTFParser.j +++ b/AppKit/CPTextView/_CPRTFParser.j @@ -21,6 +21,7 @@ @import @import +@import "CPTextStorage.j" @import "CPFontManager.j" @import "CPParagraphStyle.j" @import "_CPTableTextAttachment.j" @@ -114,11 +115,6 @@ var cp1252Map = { if (font) return font; - //Before giving up and using a default font, we try if this is - //not the case of a font with a composite name, such as - //'Helvetica-Light'. In that case, even if we don't have - //exactly an 'Helvetica-Light' font family, we might have an - //'Helvetica' one. var range = [fontName rangeOfString:@"-"]; if (range.location != CPNotFound) @@ -128,7 +124,6 @@ var cp1252Map = { font = [CPFont fontWithName:fontFamily size:fontSize]; } - /* Last resort, default font. :-( */ if (font == nil) font = [CPFont systemFontOfSize:fontSize]; @@ -184,7 +179,7 @@ var cp1252Map = { else if (type === CPRightTabStopType || type === CPRightTextAlignment) alignment = CPRightTextAlignment; else if (type === CPDecimalTabStopType) - alignment = CPRightTextAlignment; // Fallback alignment for decimal tab + alignment = CPRightTextAlignment; var tab = [[CPTextTab alloc] initWithType:alignment location:location]; @@ -222,20 +217,15 @@ var cp1252Map = { @end -// based on https://github.com/lazygyu/RTF-parser - var kRTFParserType_char = 0, kRTFParserType_dest = 1, kRTFParserType_prop = 2, kRTFParserType_spec = 3; -// Keyword descriptions var kRgsymRtf = { - // keyword dflt fPassDflt kwd idx "b" : [ "b", 1, false, kRTFParserType_prop, "propBold"], "ul" : [ "ul", 1, false, kRTFParserType_prop, "propUnderline"], "i" : [ "i", 1, false, kRTFParserType_prop, "propItalic"], -// "li" : [ "li", 0, false, kRTFParserType_prop, "propPgnFormat"], "pgnucltr" : [ "pgnucltr", "pgULtr", true, kRTFParserType_prop, "propPgnFormat"], "pgnlcltr" : [ "pgnlcltr", "pgLLtr", true, kRTFParserType_prop, "propPgnFormat"], "qc" : [ "qc", "justC", true, kRTFParserType_prop, "propJust"], @@ -277,9 +267,7 @@ var kRgsymRtf = { "ftnsep" : [ "ftnsep", 0, false, kRTFParserType_dest, "destSkip"], "ftnsepc" : [ "ftnsepc", 0, false, kRTFParserType_dest, "destSkip"], "fprq" : [ "fprq", 0, false, kRTFParserType_dest, "destSkip"], -// "fcharset" : [ "fcharset", 0, false, kRTFParserType_dest, "destSkip"], "rquote" : [ "rquote", 0, false, kRTFParserType_char, "'"], -// "s" : [ "s", 0, false, kRTFParserType_dest, "destSkip"], "header" : [ "header", 0, false, kRTFParserType_dest, "destSkip"], "headerf" : [ "headerf", 0, false, kRTFParserType_dest, "destSkip"], "headerl" : [ "headerl", 0, false, kRTFParserType_dest, "destSkip"], @@ -373,7 +361,6 @@ var kRgsymRtf = { return sym[4]; case 1: - // CPLogConsole("skipped : " + sym[4]); return ''; default: @@ -384,7 +371,6 @@ var kRgsymRtf = { - (BOOL)pushState { - // Push stack as an object containing scoping context _states.push({ curState: _curState, run: [_currentRun copy] @@ -402,7 +388,6 @@ var kRgsymRtf = { [self _flushCurrentRun]; _currentRun = state.run; - // Safety guard to prevent setting properties on null if (!_currentRun) { _currentRun = [_RTFAttribute new]; @@ -430,7 +415,6 @@ var kRgsymRtf = { case "ipfnHex": var hex = ''; - // Konsumiere exakt 2 Zeichen nach dem \' for (var i = 0; i < 2; i++) { var nextCh = _rtf.charAt(++_currentParseIndex); @@ -520,7 +504,6 @@ var kRgsymRtf = { { if (_tableRows && [_tableRows count] > 0) { - // Flush active character styling runs before appending table layout changes [self _flushCurrentRun]; var headers = [_tableRows objectAtIndex:0]; @@ -532,27 +515,16 @@ var kRgsymRtf = { var attachment = [[_CPTableTextAttachment alloc] initWithHeaders:headers rows:rows width:500.0]; - // Linear height allocation estimation - var numCols = [headers count]; - var estimatedHeight = 36.0 + ([rows count] * 28.0); - var lineCount = Math.ceil(estimatedHeight / 16.0) + 1; - var newlineStr = ""; - for (var nl = 0; nl < lineCount; nl++) { - newlineStr += "\n"; - } - - var tableAttrStr = [[CPMutableAttributedString alloc] initWithString:newlineStr]; - [tableAttrStr addAttribute:@"TableAttachmentAttribute" value:attachment range:CPMakeRange(0, [tableAttrStr length])]; - [tableAttrStr addAttribute:CPAttachmentAttributeName value:attachment range:CPMakeRange(0, [tableAttrStr length])]; + // Use standard Cocoa/Cappuccino NSAttachmentCharacter creation method + var tableAttrStr = [CPTextStorage attributedStringWithAttachment:attachment]; [_result appendAttributedString:tableAttrStr]; - // Update range offset of active attributes tracker if (_currentRun) { _currentRun._range = CPMakeRange([_result length], 0); } - + _tableRows = nil; _currentRow = nil; _currentCellText = ""; @@ -577,7 +549,6 @@ var kRgsymRtf = { [_result setAttributes:dict range:_currentRun._range]; // flush previous run - // Deep copy the current run style for the next sequence of characters _currentRun = [_currentRun copy]; } else @@ -590,8 +561,6 @@ var kRgsymRtf = { - (CPString)_applyPropChange:sym parameter:param { - //console.log("prop : " + sym[0] + " / param : " + param+ ' '); - switch (sym[0]) { case "pard": @@ -696,7 +665,6 @@ var kRgsymRtf = { if (sym[4] == "destSkip") { - CPLogConsole("Dest skip start : [" + sym[0] + "]"); _curState++; } @@ -848,8 +816,7 @@ var kRgsymRtf = { break; default: - CPLogConsole("skip : " + keyword + " param: " + param); - + break; } return ''; @@ -965,8 +932,7 @@ var kRgsymRtf = { [self _flushTableIfAny]; if ([self pushState]) - CPLogConsole("push"); - + break; case "}": @@ -974,12 +940,9 @@ var kRgsymRtf = { [self _flushTableIfAny]; if ([self popState]) - CPLogConsole("pop"); if (_freename) { - CPLogConsole(_freename); - if (_parsingFontTable) { _fontArray.push(_freename); @@ -1008,7 +971,6 @@ var kRgsymRtf = { var byteVal = parseInt(ch, 16); var unicodeVal = byteVal; - // Windows-1252 Mapping für den Bereich 0x80 - 0x9F anwenden if (byteVal >= 0x80 && byteVal <= 0x9F) { unicodeVal = cp1252Map[byteVal] || byteVal; } @@ -1044,7 +1006,6 @@ var kRgsymRtf = { if (_parsingFontTable && _freename) { var cleanFontName = _freename.trim(); - // strip family name prefix if present (e.g., "swiss Helvetica" -> "Helvetica") var lastSpaceIdx = cleanFontName.lastIndexOf(' '); if (lastSpaceIdx !== -1) { @@ -1100,7 +1061,6 @@ var kRgsymRtf = { while (i < lines.length) { var line = lines[i]; - // Tabellen-Erkennung if ([self isTableHeaderLine:line] && i + 1 < lines.length && [self isTableSeparatorLine:lines[i+1]]) { var headers = [self parseTableCells:line]; var separatorLine = lines[i+1]; @@ -1117,7 +1077,6 @@ var kRgsymRtf = { numCols = [[rows objectAtIndex:0] count]; } - // 1. Zuerst die absolute Summe der Natural-Breiten zur Spalten-Proportionsbestimmung ermitteln var totalNaturalW = 0.0; var colNaturalWidths = []; var measureTextField = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 10000.0, 24.0)]; @@ -1126,7 +1085,6 @@ var kRgsymRtf = { for (var c = 0; c < numCols; c++) { var cellW = 80.0; - // Headers prüfen if (c < headers.length) { var parsedText = [self parseInlineMarkdown:headers[c] isHeader:YES headerLevel:3]; [measureTextField setStringValue:[parsedText string]]; @@ -1134,7 +1092,6 @@ var kRgsymRtf = { cellW = Math.max(cellW, CGRectGetWidth([measureTextField frame]) + 24.0); } - // Reihen prüfen for (var r = 0; r < [rows count]; r++) { var rowData = [rows objectAtIndex:r]; if (c < [rowData count]) { @@ -1148,47 +1105,10 @@ var kRgsymRtf = { totalNaturalW += cellW; } - // 2. Präzise adaptive Zeilenhöhen-Schätzung für das Newline-Sizing (Verhindert zu große Abstände) - var estimatedHeight = 36.0; // Startwert für Header-Zeile mit Padding - for (var r = 0; r < [rows count]; r++) { - var rowData = [rows objectAtIndex:r]; - var maxCellHeight = 28.0; - - for (var c = 0; c < numCols; c++) { - var cellText = @""; - if (c < [rowData count]) { - cellText = [rowData objectAtIndex:c]; - } - var charCount = cellText.length; - - // Schätzung basierend auf realistischer Spaltenbreitenverteilung - var proportion = totalNaturalW > 0 ? (colNaturalWidths[c] / totalNaturalW) : (1.0 / numCols); - var estimatedColWidth = proportion * 500.0; - var charsPerLine = Math.max(10.0, Math.floor(estimatedColWidth / 6.5)); // ca. 6.5px pro Zeichen - - var estimatedLines = Math.ceil(charCount / charsPerLine); - if (estimatedLines < 1) estimatedLines = 1; - - var cellHeight = (estimatedLines * 16.0) + 12.0; - if (cellHeight > maxCellHeight) { - maxCellHeight = cellHeight; - } - } - estimatedHeight += maxCellHeight; - } - - // Berechne die benötigten Leerzeilen (\n Zeilenhöhe ist ca. 16px) - var lineCount = Math.ceil(estimatedHeight / 16.0) + 1; // Minimaler Sicherheitsabstand (+1) - var newlineStr = ""; - for (var nl = 0; nl < lineCount; nl++) { - newlineStr += "\n"; - } - - var tableAttrStr = [[CPMutableAttributedString alloc] initWithString:newlineStr]; var matrixView = [[_CPTableTextAttachment alloc] initWithHeaders:headers rows:rows width:500.0]; - [tableAttrStr addAttribute:@"TableAttachmentAttribute" value:matrixView range:CPMakeRange(0, [tableAttrStr length])]; - [tableAttrStr addAttribute:CPAttachmentAttributeName value:matrixView range:CPMakeRange(0, [tableAttrStr length])]; + // Render utilizing the correct atomic attachment character string + var tableAttrStr = [CPTextStorage attributedStringWithAttachment:matrixView]; [result appendAttributedString:tableAttrStr]; continue; } @@ -1196,7 +1116,6 @@ var kRgsymRtf = { var isHeader = false; var headerLevel = 0; - // Überschriften (#) var headerMatch = line.match(/^(#{1,6})\s+(.*)$/); if (headerMatch) { headerLevel = headerMatch[1].length; @@ -1204,7 +1123,6 @@ var kRgsymRtf = { isHeader = true; } - // Listenpunkte (- oder *) var isListItem = false; var listMatch = line.match(/^(\*|-)\s+(.*)$/); if (listMatch) { diff --git a/Tests/Manual/CPTextView/AppController.j b/Tests/Manual/CPTextView/AppController.j index a88dff417..3fab7a14c 100755 --- a/Tests/Manual/CPTextView/AppController.j +++ b/Tests/Manual/CPTextView/AppController.j @@ -410,11 +410,6 @@ [_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]; } // Action tied to the "Markdown ->" button to generate rich text @@ -433,7 +428,6 @@ [_textView setEditable:YES]; [_textView setString:@""]; [_textView insertText:parsedAttrStr]; - [_textView setEditable:NO]; } @end