UNPKG

html2canvas-pro

Version:

Screenshots with JavaScript. Next generation!

310 lines 13.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const assert_1 = require("assert"); const text_renderer_1 = require("../text-renderer"); const context_1 = require("../../../core/context"); const bounds_1 = require("../../../css/layout/bounds"); const text_1 = require("../../../css/layout/text"); const config_1 = require("../../../config"); const createMockContext = () => { const mockWindow = { document: { createElement: (_name) => { let _href = ''; return { set href(value) { _href = value; }, get href() { return _href; }, get protocol() { return 'http:'; }, get hostname() { return 'localhost'; }, get port() { return ''; } }; } }, location: { href: 'http://localhost/' } }; const config = new config_1.Html2CanvasConfig({ window: mockWindow }); return new context_1.Context({ logging: false, imageTimeout: 15000, useCORS: false, allowTaint: false }, new bounds_1.Bounds(0, 0, 800, 600), config); }; describe('TextRenderer', () => { it('should be instantiated', () => { const ctx = { fillStyle: '', font: '', save: () => { }, restore: () => { } }; const deps = { ctx, context: createMockContext(), options: { scale: 1 } }; const renderer = new text_renderer_1.TextRenderer(deps); (0, assert_1.ok)(renderer); // Test public methods exist (0, assert_1.strictEqual)(typeof renderer.renderTextNode, 'function'); (0, assert_1.strictEqual)(typeof renderer.renderTextWithLetterSpacing, 'function'); (0, assert_1.strictEqual)(typeof renderer.createFontStyle, 'function'); }); }); describe('hasCJKCharacters', () => { it('should return true for Chinese characters', () => { (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('快照'), true); (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('截图'), true); (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('中文'), true); }); it('should return true for Japanese characters', () => { (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('ひらがな'), true); // Hiragana (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('カタカナ'), true); // Katakana (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('漢字'), true); // Kanji }); it('should return true for Korean characters', () => { (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('한글'), true); }); it('should return true for CJK punctuation and symbols', () => { (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('。'), true); // CJK full stop (U+3002) (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('、'), true); // CJK comma (U+3001) (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('「」'), true); // CJK brackets }); it('should return true for fullwidth characters (U+FF01–U+FFEF)', () => { (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('!'), true); // Fullwidth ! (U+FF01, range start) (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('A'), true); // Fullwidth A (U+FF21) (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('1'), true); // Fullwidth 1 (U+FF11) }); it('should return false for Latin characters', () => { (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('Hello'), false); (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('SOS'), false); (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('abc123'), false); }); it('should return false for empty string', () => { (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)(''), false); }); it('should return true for mixed text containing CJK', () => { (0, assert_1.strictEqual)((0, text_renderer_1.hasCJKCharacters)('SOS 快照'), true); }); }); describe('renderTextWithLetterSpacing', () => { it('should apply letterSpacing to each character x position (Issue #73 Bug1)', () => { const fillCalls = []; const measureResults = { A: 10, B: 12, C: 8 }; const ctx = { fillStyle: '', font: '', textBaseline: 'alphabetic', fillText(text, x, y) { fillCalls.push({ text, x, y }); }, measureText(text) { return { width: measureResults[text] ?? 10 }; } }; const deps = { ctx, context: createMockContext(), options: { scale: 1 } }; const renderer = new text_renderer_1.TextRenderer(deps); const bounds = new bounds_1.Bounds(100, 50, 200, 25); const text = new text_1.TextBounds('ABC', bounds); const letterSpacing = 5; const baseline = 20; renderer.renderTextWithLetterSpacing(text, letterSpacing, baseline); // Verify letter spacing is added between characters // A: x=100, B: x=100+10+5=115, C: x=115+12+5=132 (0, assert_1.strictEqual)(fillCalls.length, 3); (0, assert_1.strictEqual)(fillCalls[0].text, 'A'); (0, assert_1.strictEqual)(fillCalls[0].x, 100); (0, assert_1.strictEqual)(fillCalls[1].text, 'B'); (0, assert_1.strictEqual)(fillCalls[1].x, 115); // 100 + measureText('A').width(10) + letterSpacing(5) (0, assert_1.strictEqual)(fillCalls[2].text, 'C'); (0, assert_1.strictEqual)(fillCalls[2].x, 132); // 115 + measureText('B').width(12) + letterSpacing(5) // Verify y position uses baseline fillCalls.forEach((call) => { (0, assert_1.strictEqual)(call.y, bounds.top + baseline); // 50 + 20 = 70 }); }); it('should use ideographic baseline for CJK characters (Issue #73 Bug2)', () => { const baselineChanges = []; let currentBaseline = 'alphabetic'; const ctx = { fillStyle: '', font: '', get textBaseline() { return currentBaseline; }, set textBaseline(value) { currentBaseline = value; baselineChanges.push(value); }, fillText(_text, _x, _y) { }, measureText(_text) { return { width: 25 }; } }; const deps = { ctx, context: createMockContext(), options: { scale: 1 } }; const renderer = new text_renderer_1.TextRenderer(deps); const bounds = new bounds_1.Bounds(0, 0, 100, 25); const text = new text_1.TextBounds('快照', bounds); renderer.renderTextWithLetterSpacing(text, 10, 20); // Should have switched to ideographic for each CJK char and restored // Pattern: [ideographic, alphabetic, ideographic, alphabetic] (0, assert_1.ok)(baselineChanges.includes('ideographic'), 'should switch to ideographic baseline for CJK'); // Should restore alphabetic after each CJK char const ideographicIdx = baselineChanges.indexOf('ideographic'); (0, assert_1.strictEqual)(baselineChanges[ideographicIdx + 1], 'alphabetic'); }); it('should not change textBaseline for non-CJK characters', () => { const baselineChanges = []; let currentBaseline = 'alphabetic'; const ctx = { fillStyle: '', font: '', get textBaseline() { return currentBaseline; }, set textBaseline(value) { currentBaseline = value; baselineChanges.push(value); }, fillText(_text, _x, _y) { }, measureText(_text) { return { width: 10 }; } }; const deps = { ctx, context: createMockContext(), options: { scale: 1 } }; const renderer = new text_renderer_1.TextRenderer(deps); const bounds = new bounds_1.Bounds(0, 0, 100, 25); const text = new text_1.TextBounds('ABC', bounds); renderer.renderTextWithLetterSpacing(text, 5, 20); // Should not switch to ideographic for Latin characters (0, assert_1.ok)(!baselineChanges.includes('ideographic'), 'should not switch to ideographic for Latin text'); }); it('should render whole string in one call when letterSpacing is 0', () => { const fillCalls = []; const ctx = { fillStyle: '', font: '', textBaseline: 'alphabetic', fillText(text, x, y) { fillCalls.push({ text, x, y }); }, measureText(_text) { return { width: 30 }; } }; const deps = { ctx, context: createMockContext(), options: { scale: 1 } }; const renderer = new text_renderer_1.TextRenderer(deps); const bounds = new bounds_1.Bounds(10, 20, 100, 25); const text = new text_1.TextBounds('Hello', bounds); renderer.renderTextWithLetterSpacing(text, 0, 22); (0, assert_1.deepStrictEqual)(fillCalls, [{ text: 'Hello', x: 10, y: 42 }]); }); it('should handle negative letterSpacing correctly', () => { const fillCalls = []; const ctx = { fillStyle: '', font: '', textBaseline: 'alphabetic', fillText(text, x, _y) { fillCalls.push({ text, x }); }, measureText(_text) { return { width: 10 }; } }; const deps = { ctx, context: createMockContext(), options: { scale: 1 } }; const renderer = new text_renderer_1.TextRenderer(deps); const bounds = new bounds_1.Bounds(100, 0, 50, 20); const text = new text_1.TextBounds('AB', bounds); renderer.renderTextWithLetterSpacing(text, -3, 15); // A: x=100, B: x=100 + 10 + (-3) = 107 (0, assert_1.strictEqual)(fillCalls[0].x, 100); (0, assert_1.strictEqual)(fillCalls[1].x, 107); }); it('should handle mixed CJK and Latin text with correct baseline per character', () => { const baselineAtRender = {}; let currentBaseline = 'alphabetic'; const ctx = { fillStyle: '', font: '', get textBaseline() { return currentBaseline; }, set textBaseline(value) { currentBaseline = value; }, fillText(text, _x, _y) { baselineAtRender[text] = currentBaseline; }, measureText(_text) { return { width: 12 }; } }; const deps = { ctx, context: createMockContext(), options: { scale: 1 } }; const renderer = new text_renderer_1.TextRenderer(deps); const bounds = new bounds_1.Bounds(0, 0, 200, 25); // Mixed: Latin 'A', CJK '快', Latin 'B' const text = new text_1.TextBounds('A快B', bounds); renderer.renderTextWithLetterSpacing(text, 5, 20); (0, assert_1.strictEqual)(baselineAtRender['A'], 'alphabetic', 'Latin char should use alphabetic baseline'); (0, assert_1.strictEqual)(baselineAtRender['快'], 'ideographic', 'CJK char should use ideographic baseline'); (0, assert_1.strictEqual)(baselineAtRender['B'], 'alphabetic', 'Latin char after CJK should restore alphabetic baseline'); }); it('should handle empty string without errors', () => { const ctx = { fillStyle: '', font: '', textBaseline: 'alphabetic', fillText(_text, _x, _y) { }, measureText(_text) { return { width: 0 }; } }; const deps = { ctx, context: createMockContext(), options: { scale: 1 } }; const renderer = new text_renderer_1.TextRenderer(deps); const bounds = new bounds_1.Bounds(0, 0, 100, 25); const text = new text_1.TextBounds('', bounds); // Should not throw renderer.renderTextWithLetterSpacing(text, 5, 20); renderer.renderTextWithLetterSpacing(text, 0, 20); }); }); //# sourceMappingURL=text-renderer.test.js.map