svelte-rtf-editor 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/rtf-parser.js +54 -10
  2. package/package.json +1 -1
@@ -228,14 +228,19 @@ export function rtfToHtml(rtfString) {
228
228
  let fontTable = new Map();
229
229
  let currentParagraph = '';
230
230
  let paragraphs = [];
231
+ let paragraphAligns = [];
231
232
  let paragraphHasText = false;
232
233
  let paragraphHasListMarker = false;
234
+ // Paragraph alignment (reset by \pard, affects cells and paragraphs)
235
+ let currentAlign = 'left';
233
236
  // Table state
234
237
  let tableRows = [];
235
238
  let currentRowCells = [];
236
239
  let currentRowIsHeader = false;
237
240
  let currentRowCellRights = [];
241
+ let currentRowCellAligns = [];
238
242
  let inTableCell = false;
243
+ let pendingBottomBorder = false; // set by \clbrdrb, confirmed by a real border type
239
244
  // First pass: extract color table and font table from the root group
240
245
  const rootGroup = tree.children[0]?.type === 'group' ? tree.children[0] : tree;
241
246
  for (let i = 0; i < rootGroup.children.length; i++) {
@@ -343,15 +348,18 @@ export function rtfToHtml(rtfString) {
343
348
  const right = row.cellRights[ci] ?? total;
344
349
  const pct = ((right - prevRight) / total * 100).toFixed(1);
345
350
  prevRight = right;
351
+ const align = row.cellAligns[ci] ?? 'left';
352
+ const alignStyle = align !== 'left' ? `;text-align:${align}` : '';
346
353
  const style = row.isHeader
347
- ? `border-bottom:1px solid;padding:2px 6px;text-align:left;width:${pct}%`
348
- : `padding:2px 6px;width:${pct}%`;
354
+ ? `border-bottom:1px solid;padding:2px 6px;text-align:${align};width:${pct}%`
355
+ : `padding:2px 6px;width:${pct}%${alignStyle}`;
349
356
  html += `<${tag} style="${style}">${row.cells[ci] || ''}</${tag}>`;
350
357
  }
351
358
  html += '</tr>';
352
359
  }
353
360
  html += '</table>';
354
361
  paragraphs.push(html);
362
+ paragraphAligns.push('left');
355
363
  tableRows = [];
356
364
  }
357
365
  function flushParagraph() {
@@ -360,14 +368,17 @@ export function rtfToHtml(rtfString) {
360
368
  if (!trimmed) {
361
369
  if (!paragraphHasListMarker) {
362
370
  paragraphs.push(currentParagraph);
371
+ paragraphAligns.push(currentAlign);
363
372
  }
364
373
  }
365
374
  else {
366
375
  paragraphs.push(currentParagraph);
376
+ paragraphAligns.push(currentAlign);
367
377
  }
368
378
  currentParagraph = '';
369
379
  paragraphHasText = false;
370
380
  paragraphHasListMarker = false;
381
+ currentAlign = 'left';
371
382
  }
372
383
  let pendingUnicodeSkip = 0;
373
384
  function walk(nodes) {
@@ -444,6 +455,18 @@ export function rtfToHtml(rtfString) {
444
455
  if (p != null)
445
456
  state.fontIndex = p;
446
457
  break;
458
+ case 'ql':
459
+ currentAlign = 'left';
460
+ break;
461
+ case 'qr':
462
+ currentAlign = 'right';
463
+ break;
464
+ case 'qc':
465
+ currentAlign = 'center';
466
+ break;
467
+ case 'qj':
468
+ currentAlign = 'justify';
469
+ break;
447
470
  case 'par':
448
471
  if (!inTableCell)
449
472
  flushParagraph();
@@ -452,19 +475,32 @@ export function rtfToHtml(rtfString) {
452
475
  currentRowCells = [];
453
476
  currentRowIsHeader = false;
454
477
  currentRowCellRights = [];
478
+ pendingBottomBorder = false;
455
479
  break;
456
480
  case 'trgaph':
457
481
  case 'trkeep':
458
482
  case 'trleft':
459
483
  case 'trhdr':
460
- case 'brdrs':
461
- case 'brdrnil':
462
484
  case 'clbrdrr':
463
485
  case 'clbrdrl':
464
486
  case 'clbrdrt':
487
+ pendingBottomBorder = false;
465
488
  break; // table formatting, ignored
466
489
  case 'clbrdrb':
467
- currentRowIsHeader = true;
490
+ pendingBottomBorder = true;
491
+ break;
492
+ case 'brdrnone':
493
+ case 'brdrnil':
494
+ pendingBottomBorder = false;
495
+ break;
496
+ case 'brdrs':
497
+ case 'brdrdb':
498
+ case 'brdrth':
499
+ case 'brdrdot':
500
+ case 'brdrdash':
501
+ if (pendingBottomBorder)
502
+ currentRowIsHeader = true;
503
+ pendingBottomBorder = false;
468
504
  break;
469
505
  case 'cellx':
470
506
  if (p != null)
@@ -475,14 +511,17 @@ export function rtfToHtml(rtfString) {
475
511
  break;
476
512
  case 'cell':
477
513
  currentRowCells.push(currentParagraph.trim());
514
+ currentRowCellAligns.push(currentAlign);
478
515
  currentParagraph = '';
479
516
  paragraphHasText = false;
517
+ currentAlign = 'left';
480
518
  break;
481
519
  case 'row':
482
- tableRows.push({ cells: currentRowCells, isHeader: currentRowIsHeader, cellRights: currentRowCellRights });
520
+ tableRows.push({ cells: currentRowCells, isHeader: currentRowIsHeader, cellRights: currentRowCellRights, cellAligns: currentRowCellAligns });
483
521
  currentRowCells = [];
484
522
  currentRowIsHeader = false;
485
523
  currentRowCellRights = [];
524
+ currentRowCellAligns = [];
486
525
  inTableCell = false;
487
526
  break;
488
527
  case 'pard':
@@ -490,6 +529,7 @@ export function rtfToHtml(rtfString) {
490
529
  state.italic = false;
491
530
  state.underline = false;
492
531
  state.strike = false;
532
+ currentAlign = 'left';
493
533
  break;
494
534
  case 'line':
495
535
  currentParagraph += '<br>';
@@ -535,6 +575,7 @@ export function rtfToHtml(rtfString) {
535
575
  case 'page':
536
576
  flushParagraph();
537
577
  paragraphs.push('<hr>');
578
+ paragraphAligns.push('left');
538
579
  break;
539
580
  case 'emspace':
540
581
  case 'enspace':
@@ -608,6 +649,7 @@ export function rtfToHtml(rtfString) {
608
649
  const isListLine = (text) => /^[-\u2022]/.test(text.replace(/^&emsp;+/g, ''));
609
650
  for (let i = 0; i < trimmedParagraphs.length; i++) {
610
651
  const trimmed = trimmedParagraphs[i];
652
+ const align = paragraphAligns[i] ?? 'left';
611
653
  const prev = i > 0 ? trimmedParagraphs[i - 1] : '';
612
654
  const next = i + 1 < trimmedParagraphs.length ? trimmedParagraphs[i + 1] : '';
613
655
  if (!trimmed) {
@@ -632,20 +674,22 @@ export function rtfToHtml(rtfString) {
632
674
  const sizeMatch = trimmed.match(/font-size:\s*([\d.]+)pt/);
633
675
  if (sizeMatch) {
634
676
  const pt = parseFloat(sizeMatch[1]);
677
+ const alignAttr = align !== 'left' ? ` style="text-align:${align}"` : '';
635
678
  if (pt >= 24) {
636
- rendered.push(`<h1>${stripSizeSpan(trimmed)}</h1>`);
679
+ rendered.push(`<h1${alignAttr}>${stripSizeSpan(trimmed)}</h1>`);
637
680
  continue;
638
681
  }
639
682
  if (pt >= 18) {
640
- rendered.push(`<h2>${stripSizeSpan(trimmed)}</h2>`);
683
+ rendered.push(`<h2${alignAttr}>${stripSizeSpan(trimmed)}</h2>`);
641
684
  continue;
642
685
  }
643
686
  if (pt >= 14) {
644
- rendered.push(`<h3>${stripSizeSpan(trimmed)}</h3>`);
687
+ rendered.push(`<h3${alignAttr}>${stripSizeSpan(trimmed)}</h3>`);
645
688
  continue;
646
689
  }
647
690
  }
648
- rendered.push(`<p>${trimmed}</p>`);
691
+ const pStyle = align !== 'left' ? ` style="text-align:${align}"` : '';
692
+ rendered.push(`<p${pStyle}>${trimmed}</p>`);
649
693
  }
650
694
  const output = rendered.join('\n');
651
695
  // fontTable is parsed (for future font rendering) but not yet used in output
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-rtf-editor",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "RTF viewer and rich-text editor components for Svelte 5",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/jcelano/svelte-rtf-editor#readme",