CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-zrender

A lightweight 2D graphics library providing canvas and SVG rendering for Apache ECharts

Overview
Eval results
Files

text-images.mddocs/

Text and Image Rendering

ZRender provides comprehensive text rendering capabilities with rich typography support and efficient bitmap image display. The text system supports multi-line text, rich formatting, alignment options, and advanced features like text truncation and custom styling.

Text Rendering

Text Class

The primary class for rendering text content:

class Text extends Displayable {
  constructor(opts: TextProps);
  style: TextStyleProps;
}

interface TextProps extends DisplayableProps {
  style?: TextStyleProps;
}

interface TextState {
  style?: Partial<TextStyleProps>;
}

Text Styling Properties

interface TextStyleProps {
  // Content and basic typography
  text?: string;
  fontSize?: number;
  fontFamily?: string;
  fontStyle?: 'normal' | 'italic' | 'oblique';
  fontWeight?: string | number;
  
  // Text positioning and alignment
  textAlign?: 'left' | 'center' | 'right';
  textVerticalAlign?: 'top' | 'middle' | 'bottom';
  textBaseline?: 'top' | 'middle' | 'bottom' | 'alphabetic' | 'ideographic' | 'hanging';
  
  // Visual appearance
  fill?: string | LinearGradient | RadialGradient | Pattern;
  stroke?: string;
  lineWidth?: number;
  opacity?: number;
  
  // Text shadows
  textShadowBlur?: number;
  textShadowColor?: string;
  textShadowOffsetX?: number;
  textShadowOffsetY?: number;
  
  // Layout and spacing
  width?: number;
  height?: number;
  textPadding?: number | number[];
  textLineHeight?: number;
  
  // Rich text formatting
  rich?: Record<string, TextStyleProps>;
  
  // Text truncation
  truncate?: {
    outerWidth?: number;
    outerHeight?: number;
    ellipsis?: string;
    placeholder?: string;
  };
  
  // Advanced styling
  textBorderColor?: string;
  textBorderWidth?: number;
  textBackgroundColor?: string;
  backgroundColor?: string;
  borderColor?: string;
  borderWidth?: number;
  borderRadius?: number;
  padding?: number | number[];
  
  // Rendering effects
  blend?: string;
}

TSpan Class

Text span element for rich text components:

class TSpan extends Displayable {
  constructor(opts: TSpanProps);
  style: TSpanStyleProps;
}

interface TSpanProps extends DisplayableProps {
  style?: TSpanStyleProps;
}

interface TSpanStyleProps extends TextStyleProps {
  x?: number;
  y?: number;
  text?: string;
}

interface TSpanState {
  style?: Partial<TSpanStyleProps>;
}

Image Rendering

Image Class

Display bitmap images with complete control over positioning and effects:

class Image extends Displayable {
  constructor(opts: ImageProps);
  style: ImageStyleProps;
}

interface ImageProps extends DisplayableProps {
  style?: ImageStyleProps;
}

interface ImageState {
  style?: Partial<ImageStyleProps>;
}

Image Styling Properties

interface ImageStyleProps {
  // Image source
  image?: string | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement;
  
  // Positioning and sizing
  x?: number;
  y?: number;
  width?: number;
  height?: number;
  
  // Source cropping (sprite/atlas support)
  sx?: number;      // Source x position
  sy?: number;      // Source y position
  sWidth?: number;  // Source width
  sHeight?: number; // Source height
  
  // Visual effects
  opacity?: number;
  shadowBlur?: number;
  shadowColor?: string;
  shadowOffsetX?: number;
  shadowOffsetY?: number;
  
  // Advanced rendering
  blend?: string;
  globalCompositeOperation?: string;
}

Usage Examples

Basic Text Rendering

import { Text } from "zrender";

// Simple text
const simpleText = new Text({
  style: {
    text: 'Hello ZRender!',
    fontSize: 24,
    fontFamily: 'Arial, sans-serif',
    fill: '#2d3436',
    textAlign: 'center'
  },
  position: [200, 100]
});

// Styled text with shadow
const styledText = new Text({
  style: {
    text: 'Styled Text',
    fontSize: 32,
    fontWeight: 'bold',
    fill: '#74b9ff',
    stroke: '#0984e3',
    lineWidth: 2,
    textShadowBlur: 5,
    textShadowColor: 'rgba(0, 0, 0, 0.3)',
    textShadowOffsetX: 2,
    textShadowOffsetY: 2
  },
  position: [200, 200]
});

zr.add(simpleText);
zr.add(styledText);

Multi-line Text and Layout

import { Text } from "zrender";

// Multi-line text with layout control
const multilineText = new Text({
  style: {
    text: 'This is a long text that will wrap to multiple lines when the width is constrained.',
    fontSize: 16,
    fontFamily: 'Arial, sans-serif',
    fill: '#2d3436',
    width: 250,              // Constrain width for wrapping
    textLineHeight: 20,      // Line height
    textAlign: 'left',
    textVerticalAlign: 'top',
    padding: [10, 15],       // Internal padding
    backgroundColor: '#f8f9fa',
    borderColor: '#dee2e6',
    borderWidth: 1,
    borderRadius: 5
  },
  position: [50, 50]
});

// Text with specific alignment
const alignedText = new Text({
  style: {
    text: 'Center Aligned\nMultiple Lines\nOf Text',
    fontSize: 18,
    fill: '#6c757d',
    textAlign: 'center',
    textVerticalAlign: 'middle',
    width: 200,
    height: 100,
    backgroundColor: '#e9ecef',
    borderRadius: 8
  },
  position: [350, 50]
});

zr.add(multilineText);
zr.add(alignedText);

Rich Text Formatting

import { Text } from "zrender";

// Rich text with multiple styles
const richText = new Text({
  style: {
    text: '{title|ZRender Graphics}\n{subtitle|Powerful 2D Rendering}\n{body|Create beautiful graphics with ease}\n{link|Learn More →}',
    rich: {
      title: {
        fontSize: 28,
        fontWeight: 'bold',
        fill: '#2d3436',
        textAlign: 'center'
      },
      subtitle: {
        fontSize: 16,
        fill: '#74b9ff',
        fontStyle: 'italic',
        textAlign: 'center',
        textPadding: [5, 0, 10, 0]
      },
      body: {
        fontSize: 14,
        fill: '#636e72',
        textAlign: 'center',
        textPadding: [0, 0, 15, 0]
      },
      link: {
        fontSize: 14,
        fill: '#00b894',
        fontWeight: 'bold',
        textAlign: 'center',
        textBorderColor: '#00b894',
        textBorderWidth: 1,
        textPadding: [5, 10],
        textBackgroundColor: 'rgba(0, 184, 148, 0.1)',
        borderRadius: 3
      }
    },
    width: 300,
    textAlign: 'center'
  },
  position: [50, 250]
});

zr.add(richText);

Text Truncation and Overflow

import { Text } from "zrender";

// Text with ellipsis truncation
const truncatedText = new Text({
  style: {
    text: 'This is a very long text that will be truncated with ellipsis when it exceeds the specified width',
    fontSize: 14,
    fill: '#2d3436',
    width: 200,
    truncate: {
      outerWidth: 200,
      ellipsis: '...',
      placeholder: 'No content'
    }
  },
  position: [400, 250]
});

// Text with custom truncation
const customTruncatedText = new Text({
  style: {
    text: 'Another long text example',
    fontSize: 16,
    fill: '#e17055',
    width: 150,
    truncate: {
      outerWidth: 150,
      ellipsis: ' [more]',
      placeholder: '[empty]'
    }
  },
  position: [400, 300]
});

zr.add(truncatedText);
zr.add(customTruncatedText);

Gradient and Pattern Text

import { Text, LinearGradient, Pattern } from "zrender";

// Gradient fill text
const gradientText = new Text({
  style: {
    text: 'GRADIENT',
    fontSize: 48,
    fontWeight: 'bold',
    fill: new LinearGradient(0, 0, 1, 0, [
      { offset: 0, color: '#ff7675' },
      { offset: 0.5, color: '#fd79a8' },
      { offset: 1, color: '#fdcb6e' }
    ]),
    stroke: '#2d3436',
    lineWidth: 2
  },
  position: [50, 400]
});

// Pattern fill text (if you have a pattern image)
const createTextPattern = () => {
  const canvas = document.createElement('canvas');
  canvas.width = 20;
  canvas.height = 20;
  const ctx = canvas.getContext('2d')!;
  
  // Create diagonal stripes pattern
  ctx.strokeStyle = '#74b9ff';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(0, 0);
  ctx.lineTo(20, 20);
  ctx.moveTo(0, 20);
  ctx.lineTo(20, 0);
  ctx.stroke();
  
  return new Pattern(canvas, 'repeat');
};

const patternText = new Text({
  style: {
    text: 'PATTERN',
    fontSize: 48,
    fontWeight: 'bold',
    fill: createTextPattern(),
    stroke: '#0984e3',
    lineWidth: 1
  },
  position: [350, 400]
});

zr.add(gradientText);
zr.add(patternText);

Basic Image Display

import { Image } from "zrender";

// Display image from URL
const imageFromUrl = new Image({
  style: {
    image: 'https://example.com/image.jpg',
    x: 50,
    y: 50,
    width: 200,
    height: 150
  }
});

// Display image from HTMLImageElement
const createImageElement = (src: string) => {
  const img = new Image();
  img.src = src;
  return img;
};

const imageFromElement = new Image({
  style: {
    image: createImageElement('path/to/image.png'),
    x: 300,
    y: 50,
    width: 150,
    height: 150,
    opacity: 0.8
  }
});

zr.add(imageFromUrl);
zr.add(imageFromElement);

Image Effects and Transformations

import { Image } from "zrender";

// Image with shadow effect
const shadowImage = new Image({
  style: {
    image: 'path/to/image.jpg',
    x: 50,
    y: 250,
    width: 120,
    height: 120,
    shadowBlur: 15,
    shadowColor: 'rgba(0, 0, 0, 0.4)',
    shadowOffsetX: 5,
    shadowOffsetY: 5
  }
});

// Sprite/Atlas usage (cropping from larger image)
const spriteImage = new Image({
  style: {
    image: 'path/to/spritesheet.png',
    x: 200,
    y: 250,
    width: 64,
    height: 64,
    // Crop from sprite sheet
    sx: 128,      // Source x in spritesheet
    sy: 64,       // Source y in spritesheet  
    sWidth: 64,   // Source width
    sHeight: 64   // Source height
  }
});

// Animated image scaling
const scalingImage = new Image({
  style: {
    image: 'path/to/image.jpg',
    x: 350,
    y: 250,
    width: 80,
    height: 80
  }
});

// Animate image scaling on hover
scalingImage.on('mouseover', () => {
  scalingImage.animate('style')
    .when(300, { width: 120, height: 120, x: 330, y: 230 })
    .start('easeOutQuad');
});

scalingImage.on('mouseout', () => {
  scalingImage.animate('style')
    .when(300, { width: 80, height: 80, x: 350, y: 250 })
    .start('easeOutQuad');
});

zr.add(shadowImage);
zr.add(spriteImage);
zr.add(scalingImage);

Canvas and Video as Image Sources

import { Image } from "zrender";

// Use canvas as image source
const createCanvasImage = () => {
  const canvas = document.createElement('canvas');
  canvas.width = 100;
  canvas.height = 100;
  const ctx = canvas.getContext('2d')!;
  
  // Draw something on canvas
  const gradient = ctx.createLinearGradient(0, 0, 100, 100);
  gradient.addColorStop(0, '#ff7675');
  gradient.addColorStop(1, '#74b9ff');
  
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, 100, 100);
  
  ctx.fillStyle = '#ffffff';
  ctx.font = '16px Arial';
  ctx.textAlign = 'center';
  ctx.fillText('Canvas', 50, 55);
  
  return canvas;
};

const canvasImage = new Image({
  style: {
    image: createCanvasImage(),
    x: 50,
    y: 400,
    width: 100,
    height: 100
  }
});

// Use video as image source (for video frames)
const videoImage = new Image({
  style: {
    image: document.getElementById('videoElement') as HTMLVideoElement,
    x: 200,
    y: 400,
    width: 160,
    height: 90
  }
});

zr.add(canvasImage);
// zr.add(videoImage); // Only if video element exists

Interactive Text and Images

import { Text, Image, Group } from "zrender";

// Create interactive card with text and image
const createInteractiveCard = (title: string, description: string, imageSrc: string, x: number, y: number) => {
  const card = new Group({
    position: [x, y]
  });
  
  // Background
  const background = new Rect({
    shape: { x: 0, y: 0, width: 250, height: 150, r: 8 },
    style: {
      fill: '#ffffff',
      stroke: '#dee2e6',
      lineWidth: 1,
      shadowBlur: 10,
      shadowColor: 'rgba(0, 0, 0, 0.1)'
    }
  });
  
  // Image
  const image = new Image({
    style: {
      image: imageSrc,
      x: 15,
      y: 15,
      width: 60,
      height: 60
    }
  });
  
  // Title text
  const titleText = new Text({
    style: {
      text: title,
      fontSize: 18,
      fontWeight: 'bold',
      fill: '#2d3436',
      textAlign: 'left'
    },
    position: [90, 25]
  });
  
  // Description text
  const descText = new Text({
    style: {
      text: description,
      fontSize: 14,
      fill: '#636e72',
      width: 145,
      textAlign: 'left'
    },
    position: [90, 50]
  });
  
  // Build card
  card.add(background);
  card.add(image);
  card.add(titleText);
  card.add(descText);
  
  // Add hover effects
  card.on('mouseover', () => {
    background.animate('style')
      .when(200, { shadowBlur: 20, fill: '#f8f9fa' })
      .start();
  });
  
  card.on('mouseout', () => {
    background.animate('style')
      .when(200, { shadowBlur: 10, fill: '#ffffff' })
      .start();
  });
  
  return card;
};

// Create interactive cards
const card1 = createInteractiveCard(
  'ZRender Graphics',
  'Powerful 2D rendering library for creating interactive visualizations.',
  'path/to/zrender-icon.png',
  50, 500
);

const card2 = createInteractiveCard(
  'Canvas Rendering',
  'High-performance canvas-based rendering with full feature support.',
  'path/to/canvas-icon.png',
  350, 500
);

zr.add(card1);
zr.add(card2);

Text Measurement and Layout

import { Text } from "zrender";

// Dynamic text sizing based on content
const createAdaptiveText = (content: string, maxWidth: number) => {
  let fontSize = 20;
  let textElement: Text;
  
  // Function to estimate text width (approximate)
  const estimateTextWidth = (text: string, size: number) => {
    return text.length * size * 0.6; // Rough approximation
  };
  
  // Adjust font size to fit width
  while (estimateTextWidth(content, fontSize) > maxWidth && fontSize > 8) {
    fontSize -= 1;
  }
  
  textElement = new Text({
    style: {
      text: content,
      fontSize: fontSize,
      fill: '#2d3436',
      width: maxWidth,
      textAlign: 'center'
    }
  });
  
  return textElement;
};

// Create texts that adapt to container width
const adaptiveText1 = createAdaptiveText('Short text', 200);
const adaptiveText2 = createAdaptiveText('This is a much longer text that should be sized down', 200);

adaptiveText1.position = [50, 650];
adaptiveText2.position = [300, 650];

zr.add(adaptiveText1);
zr.add(adaptiveText2);

Install with Tessl CLI

npx tessl i tessl/npm-zrender

docs

animation.md

core-zrender.md

events.md

graphics-primitives.md

index.md

shapes.md

styling.md

text-images.md

utilities.md

tile.json