Font Family Node Type

Category: Formatting • Detailed implementation guide with view integration notes

Schema Definition

Schema definition for the Font Family mark type:

{
  fontFamily: {
    attrs: {
      family: { default: 'Arial' }
    },
      style: 'font-family: ' + node.attrs.family 
    }, 0]
  }
}

Model Representation

Example model representation:

{
  type: 'text',
  text: 'Monospace text',
  marks: [{
    type: 'fontFamily',
    attrs: { family: 'monospace' }
  }]
}

HTML Serialization

Converting model to HTML:

function serializeFontFamilyMark(text, mark) {
  const family = mark.attrs?.family || 'Arial';
  return '<span style="font-family: ' + escapeHtml(family) + '">' + 
         text + '</span>';
}

HTML Deserialization

Parsing HTML to model:

function extractFontFamilyMark(element) {
  if (element.tagName === 'SPAN' && element.style.fontFamily) {
    const family = extractFontFamily(element.style.fontFamily);
    return {
      type: 'fontFamily',
      attrs: { family }
    };
  }
  return null;
}

function extractFontFamily(fontFamilyString) {
  // Extract first font from font-family string
  // e.g., "Arial, sans-serif" -> "Arial"
  return fontFamilyString.split(',')[0].trim().replace(/['"]/g, '');
}

View Integration

View Integration Notes: Pay special attention to contenteditable behavior, selection handling, and event management when implementing this node type in your view layer.

View integration code:

// Setting font family
function setFontFamily(family) {
  addMark({
    type: 'fontFamily',
    attrs: { family }
  });
}

// Font family selector
function showFontFamilyPicker() {
  const families = ['Arial', 'Times New Roman', 'Courier New', 'monospace'];
  // Show font selector
}

Common Issues

Common Pitfalls: These are issues frequently encountered when implementing this node type. Review carefully before implementation.

Common issues and solutions:

// Issue: Font family with quotes
// Solution: Handle quoted font names
function normalizeFontFamily(family) {
  return family.replace(/['"]/g, '');
}

// Issue: Font fallback chain
// Solution: Extract primary font from fallback chain
function extractPrimaryFont(fontFamily) {
  return fontFamily.split(',')[0].trim();
}

// Issue: Web-safe fonts
// Solution: Validate font availability
function validateFontFamily(family) {
  const webSafeFonts = [
    'Arial', 'Times New Roman', 'Courier New', 
    'Verdana', 'Georgia', 'Palatino'
  ];
  return webSafeFonts.includes(family) || family === 'monospace';
}

Implementation

Complete implementation example:

class FontFamilyMark {
  constructor(attrs) {
    this.type = 'fontFamily';
    this.attrs = { family: attrs?.family || 'Arial' };
  }
  
  toDOM() {
    return ['span', { 
      style: 'font-family: ' + this.attrs.family 
    }, 0];
  }
  
  static fromDOM(element) {
    if (element.tagName === 'SPAN' && element.style.fontFamily) {
      return new FontFamilyMark({ 
        family: extractFontFamily(element.style.fontFamily) 
      });
    }
    return null;
  }
}