From 3d463c91d8d4e179beabbc0d54b6b694abbfaac4 Mon Sep 17 00:00:00 2001 From: biss Date: Sat, 25 Apr 2026 14:22:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=A0=B7=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E6=96=87=E5=AD=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gen/css/main.css | 331 +++++++++++++++++++++++++++++++++++++---------- gen/index.html | 5 +- gen/js/paint.js | 315 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 552 insertions(+), 99 deletions(-) diff --git a/gen/css/main.css b/gen/css/main.css index 2836512..18b19dd 100644 --- a/gen/css/main.css +++ b/gen/css/main.css @@ -1,8 +1,19 @@ :root { - --primary-color: #0d6efd; - --primary-hover: #0b5ed7; - --secondary-color: #6c757d; - --secondary-hover: #5c636a; + --primary-color: #0f62fe; + --primary-hover: #3b82ff; + --secondary-color: #3b485f; + --secondary-hover: #52627d; + --accent-color: #22d3ee; + --accent-soft: rgba(34, 211, 238, 0.18); + --surface-color: rgba(255, 255, 255, 0.8); + --surface-strong: rgba(255, 255, 255, 0.92); + --border-color: rgba(109, 128, 162, 0.22); + --text-color: #10203a; + --muted-text: #5e6c84; + --shadow-soft: 0 20px 45px rgba(15, 35, 95, 0.12); + --shadow-strong: 0 18px 50px rgba(15, 98, 254, 0.18); + --page-glow-1: rgba(15, 98, 254, 0.2); + --page-glow-2: rgba(34, 211, 238, 0.18); --dark-bg: #121212; --dark-text: #e0e0e0; @@ -17,64 +28,132 @@ body { margin: 0; padding: 0; - font-family: system-ui, -apple-system, sans-serif; + font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; + color: var(--text-color); + background: + radial-gradient(circle at top left, var(--page-glow-1), transparent 32%), + radial-gradient(circle at top right, var(--page-glow-2), transparent 28%), + linear-gradient(135deg, #eef5ff 0%, #f6fbff 45%, #f8f9fd 100%); + min-height: 100vh; + position: relative; +} + +body::before, +body::after { + content: ""; + position: fixed; + inset: auto; + width: 42vw; + height: 42vw; + border-radius: 50%; + pointer-events: none; + filter: blur(60px); + opacity: 0.45; + z-index: 0; +} + +body::before { + top: -12vw; + right: -10vw; + background: rgba(15, 98, 254, 0.12); +} + +body::after { + bottom: -14vw; + left: -8vw; + background: rgba(34, 211, 238, 0.1); } button { - padding: 0.375rem 0.75rem; - border: 1px solid var(--primary-color); - border-radius: 0.375rem; + padding: 0.5rem 0.95rem; + border: 1px solid transparent; + border-radius: 8px; margin-bottom: 5px; white-space: nowrap; cursor: pointer; font-size: 0.9rem; + font-weight: 600; + letter-spacing: 0.01em; + transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease; + box-shadow: 0 10px 20px rgba(16, 32, 58, 0.08); + backdrop-filter: blur(8px); +} + +button:hover { + transform: translateY(-1px); } button:disabled { opacity: 0.65; + cursor: not-allowed; + box-shadow: none; } button.primary { color: #fff; - background-color: var(--primary-color); + background: linear-gradient(135deg, var(--primary-color), #2388ff); + box-shadow: 0 12px 24px rgba(15, 98, 254, 0.28); } button.primary:hover { color: #fff; - border-color: var(--primary-hover); - background-color: var(--primary-hover); + border-color: rgba(255, 255, 255, 0.3); + background: linear-gradient(135deg, var(--primary-hover), #45c3ff); + box-shadow: 0 16px 30px rgba(15, 98, 254, 0.34); } button.secondary { - color: #fff; - background-color: var(--secondary-color); - border-color: var(--secondary-color); + color: var(--text-color); + background: rgba(255, 255, 255, 0.72); + border-color: rgba(109, 128, 162, 0.22); } button.secondary:hover { - color: #fff; - border-color: var(--secondary-hover); - background-color: var(--secondary-hover); + color: var(--text-color); + border-color: rgba(59, 72, 95, 0.24); + background: rgba(255, 255, 255, 0.95); } h3 { - padding-bottom: .3em; - border-bottom: 1px solid #ccc; + padding: 1.6rem 0 1rem; + margin: 0 0 1.25rem; + border-bottom: 1px solid rgba(131, 149, 186, 0.26); text-align: center; + font-size: clamp(1.5rem, 2vw, 2rem); + font-weight: 800; + letter-spacing: 0.04em; + color: #0f1f44; + text-shadow: 0 10px 25px rgba(15, 98, 254, 0.12); } fieldset { border: none; - box-shadow: 0 .5rem 0.5rem rgba(0, 0, 0, 0.2); - background-color: #f8f9fa; - padding: 10px; - margin-bottom: 16px; - border-radius: 4px; + box-shadow: var(--shadow-soft); + background: linear-gradient(180deg, var(--surface-strong), var(--surface-color)); + padding: 16px 16px 12px; + margin-bottom: 18px; + border-radius: 10px; + border: 1px solid var(--border-color); + position: relative; + overflow: hidden; +} + +fieldset::before { + content: ""; + position: absolute; + inset: 0 auto auto 0; + width: 100%; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(34, 211, 238, 0.65), transparent); + opacity: 0.85; } fieldset legend { font-weight: bold; - color: rgba(0, 0, 255, 0.6); + color: #1862d9; + padding: 0 10px; + font-size: 0.95rem; + letter-spacing: 0.08em; } code { @@ -86,49 +165,66 @@ code { } input[type=text], +textarea, input[type=date], input[type=number], select { font-size: 1rem; font-weight: 400; line-height: 1.5; - color: #212529; - border: 1px solid #dee2e6; - border-radius: 0.375rem; - padding: .2rem .75rem; + color: var(--text-color); + border: 1px solid rgba(121, 140, 176, 0.26); + border-radius: 6px; + padding: .45rem .85rem; max-width: 100%; box-sizing: border-box; + background: rgba(255, 255, 255, 0.86); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6); + transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease; } input[type=file] { font-size: 1rem; font-weight: 400; line-height: 1.5; - color: #212529; + color: var(--text-color); max-width: 100%; } input::file-selector-button { font-size: 0.9rem; - font-weight: 400; + font-weight: 600; line-height: 1.5; - border: 1px solid var(--primary-color); - border-radius: 0.375rem; + border: 1px solid rgba(121, 140, 176, 0.26); + border-radius: 8px; cursor: pointer; + padding: 0.45rem 0.95rem; + margin-right: 10px; + color: var(--text-color); + background: rgba(255, 255, 255, 0.9); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +input::file-selector-button:hover { + transform: translateY(-1px); + box-shadow: 0 10px 20px rgba(16, 32, 58, 0.08); } select { - padding: .3rem 2.25rem .3rem .75rem; + padding: .45rem 2.25rem .45rem .85rem; } input:focus, +textarea:focus, select:focus { - border: 1px solid #86b7fe; - box-shadow: 0 0 4px rgba(0, 120, 215, 0.8); + border: 1px solid rgba(15, 98, 254, 0.55); + box-shadow: 0 0 0 4px rgba(15, 98, 254, 0.14); outline: 0; + background: #fff; } input[type=text]:disabled, +textarea:disabled, input[type=date]:disabled, input[type=number]:disabled, select:disabled { @@ -141,27 +237,32 @@ select:disabled { label { margin-right: 4px; white-space: nowrap; + color: var(--muted-text); + font-weight: 600; } .main { width: 100%; max-width: 950px; margin: 0 auto; - padding: 0 1rem; - background: #fff; + padding: 0 1rem 1.5rem; + background: transparent; font-size: 1rem; font-weight: 400; line-height: 1.5; box-sizing: border-box; + position: relative; + z-index: 1; } .footer { display: flex; gap: 10px; font-size: 0.8rem; - color: #666; + color: var(--muted-text); flex-wrap: wrap; - margin: 1rem 0; + margin: 1rem 0 2rem; + justify-content: center; } .footer .links { @@ -170,7 +271,7 @@ label { } .footer .links a { - color: #666; + color: var(--muted-text); text-decoration: none; position: relative; padding: 0 8px; @@ -188,7 +289,7 @@ label { } .footer a:hover { - color: #0d6efd; + color: var(--primary-color); text-decoration: underline; } @@ -219,13 +320,17 @@ label { min-height: 100px; max-height: 300px; margin: 0; - padding: 5px; - background: #ddd; + padding: 10px 12px; + background: linear-gradient(180deg, rgba(14, 26, 48, 0.92), rgba(20, 36, 63, 0.96)); + color: #d9ecff; overflow-y: auto; overflow-x: hidden; font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; box-sizing: border-box; word-break: break-word; + border-radius: 16px; + border: 1px solid rgba(77, 102, 150, 0.4); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06); } .log-container .log-line { @@ -239,21 +344,22 @@ label { } .log-container .time { - color: #333; + color: #7bdcff; margin-right: 0.5em; } .log-container .action { - color: #666; + color: #95aecd; margin-right: 0.5em; } .template-panel { - border: 1px solid #dee2e6; - border-radius: 0.5rem; - background: #fff; + border: 1px solid rgba(121, 140, 176, 0.22); + border-radius: 8px; + background: rgba(255, 255, 255, 0.78); padding: 12px; margin-bottom: 10px; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55); } .panel-tabs { @@ -264,20 +370,21 @@ label { } .panel-tab { - color: #495057; - background: #eef2f6; - border-color: #d0d7de; + color: var(--muted-text); + background: rgba(255, 255, 255, 0.7); + border-color: rgba(121, 140, 176, 0.18); } .panel-tab:hover { - background: #dde5ee; - border-color: #c1c9d2; + background: rgba(255, 255, 255, 0.96); + border-color: rgba(121, 140, 176, 0.26); } .panel-tab.active { color: #fff; - background: var(--primary-color); - border-color: var(--primary-color); + background: linear-gradient(135deg, var(--primary-color), #1ec8ff); + border-color: transparent; + box-shadow: 0 12px 24px rgba(15, 98, 254, 0.2); } .panel-tab-content { @@ -297,7 +404,7 @@ label { } .template-header span { - color: #666; + color: var(--muted-text); font-size: 0.9rem; } @@ -317,9 +424,9 @@ label { gap: 8px; align-items: center; padding: 8px; - border: 1px solid #dee2e6; - border-radius: 0.375rem; - background: #f8f9fa; + border: 1px solid rgba(121, 140, 176, 0.18); + border-radius: 6px; + background: rgba(248, 251, 255, 0.86); } .template-item input[type="text"] { @@ -338,11 +445,17 @@ label { } .canvas-container canvas { - border: black solid 1px; + border: 1px solid rgba(37, 61, 104, 0.24); max-width: 100%; height: auto; display: block; margin: 0 auto; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(245, 249, 255, 0.98)); + border-radius: 0; + box-shadow: + 0 25px 60px rgba(15, 35, 95, 0.18), + inset 0 1px 0 rgba(255, 255, 255, 0.8); } .canvas-container.crop-mode canvas { @@ -353,10 +466,10 @@ label { .status-bar { display: none; font-size: 85%; - color: #666; + color: var(--muted-text); margin-bottom: 10px; padding-bottom: 10px; - border-bottom: 1px dotted #AAA; + border-bottom: 1px dotted rgba(121, 140, 176, 0.45); } canvas.text-placement-mode { @@ -371,6 +484,15 @@ canvas.text-placement-mode { color: var(--primary-color); } +.canvas-container { + padding: 18px; + border-radius: 0; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.74), rgba(242, 248, 255, 0.84)); + border: 1px solid rgba(121, 140, 176, 0.18); + box-shadow: var(--shadow-soft); +} + .canvas-tools { margin-top: 10px; justify-content: center; @@ -382,6 +504,27 @@ canvas.text-placement-mode { display: none; } +.markdown-text-input-group { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 240px; + flex: 1 1 280px; +} + +#text-input { + width: 100%; + min-height: 110px; + resize: vertical; + font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; +} + +.markdown-text-hint { + color: var(--muted-text); + font-size: 0.82rem; + line-height: 1.4; +} + .canvas-container.brush-mode .brush-tools, .canvas-container.text-mode .brush-tools, .canvas-container.eraser-mode .brush-tools, @@ -404,23 +547,25 @@ canvas.text-placement-mode { align-items: center; justify-content: center; margin-right: 5px; - background-color: #f8f9fa; - border: 1px solid #dee2e6; + background: rgba(255, 255, 255, 0.86); + border: 1px solid rgba(121, 140, 176, 0.22); border-radius: 4px; padding: 0; cursor: pointer; transition: all 0.2s ease; + box-shadow: 0 8px 18px rgba(16, 32, 58, 0.08); } .tool-button:hover { - background-color: #e9ecef; - border-color: #ced4da; + background: #fff; + border-color: rgba(121, 140, 176, 0.3); } .tool-button.active { - background-color: var(--primary-color); + background: linear-gradient(135deg, var(--primary-color), #1ec8ff); color: white; - border-color: var(--primary-color); + border-color: transparent; + box-shadow: 0 12px 24px rgba(15, 98, 254, 0.24); } .tool-button.hide { @@ -474,6 +619,7 @@ canvas.text-placement-mode { } input[type=text], + textarea, input[type=number], select { max-width: 100%; @@ -491,14 +637,23 @@ body.dark-mode .main { color: var(--dark-text); } +body.dark-mode { + background: + radial-gradient(circle at top left, rgba(15, 98, 254, 0.16), transparent 32%), + radial-gradient(circle at bottom right, rgba(34, 211, 238, 0.14), transparent 30%), + linear-gradient(135deg, #0b1220 0%, #10192d 50%, #0c1220 100%); +} + body.dark-mode fieldset { background-color: var(--dark-fieldset-bg); box-shadow: 0 .5rem 0.5rem rgba(0, 0, 0, 0.5); + border: 1px solid rgba(77, 102, 150, 0.24); } body.dark-mode h3 { border-bottom: 1px solid var(--dark-border); color: var(--dark-text); + text-shadow: 0 12px 30px rgba(34, 211, 238, 0.12); } body.dark-mode code { @@ -507,6 +662,7 @@ body.dark-mode code { } body.dark-mode input[type=text], +body.dark-mode textarea, body.dark-mode input[type=date], body.dark-mode input[type=number], body.dark-mode select { @@ -516,6 +672,7 @@ body.dark-mode select { } body.dark-mode input[type=text]:disabled, +body.dark-mode textarea:disabled, body.dark-mode input[type=date]:disabled, body.dark-mode input[type=number]:disabled, body.dark-mode select:disabled { @@ -597,6 +754,15 @@ body.dark-mode .template-item { border-color: var(--dark-border); } +body.dark-mode .canvas-container { + background: linear-gradient(180deg, rgba(23, 31, 49, 0.92), rgba(17, 24, 39, 0.95)); + border-color: rgba(77, 102, 150, 0.24); +} + +body.dark-mode .canvas-container canvas { + background: #f8fbff; +} + body.dark-mode .panel-tab { color: var(--dark-text); background-color: #2a2a2a; @@ -617,3 +783,30 @@ body.dark-mode .panel-tab.active { body.dark-mode .template-header span { color: #aaa; } + +@media (prefers-reduced-motion: no-preference) { + fieldset, + .canvas-container { + animation: floatIn 0.6s ease both; + } + + fieldset:nth-of-type(2) { + animation-delay: 0.06s; + } + + fieldset:nth-of-type(3) { + animation-delay: 0.12s; + } +} + +@keyframes floatIn { + from { + opacity: 0; + transform: translateY(16px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/gen/index.html b/gen/index.html index 7ccd1b1..a57b5ae 100644 --- a/gen/index.html +++ b/gen/index.html @@ -299,7 +299,10 @@
- +
+ +
支持标题、列表、引用、粗体、斜体、删除线和行内代码
+
diff --git a/gen/js/paint.js b/gen/js/paint.js index e5cd543..2950187 100644 --- a/gen/js/paint.js +++ b/gen/js/paint.js @@ -40,6 +40,272 @@ class PaintManager { this.hideBrushCursor = this.hideBrushCursor.bind(this); } + buildFontString(fontFamily, fontSize, bold = false, italic = false) { + const fontParts = []; + if (italic) fontParts.push('italic'); + if (bold) fontParts.push('bold'); + fontParts.push(`${fontSize}px`); + fontParts.push(fontFamily); + return fontParts.join(' '); + } + + parseInlineMarkdown(text, baseStyle = {}) { + const patterns = [ + { + regex: /`([^`]+)`/g, + style: { code: true, fontFamily: 'monospace', background: 'rgba(0, 0, 0, 0.08)' } + }, + { regex: /\*\*([^*]+)\*\*/g, style: { bold: true } }, + { regex: /__([^_]+)__/g, style: { bold: true } }, + { regex: /\*([^*]+)\*/g, style: { italic: true } }, + { regex: /_([^_]+)_/g, style: { italic: true } }, + { regex: /~~([^~]+)~~/g, style: { strike: true, color: '#666666' } } + ]; + + let segments = [{ text, style: { ...baseStyle } }]; + + patterns.forEach(({ regex, style }) => { + const nextSegments = []; + segments.forEach((segment) => { + if (!segment.text) return; + regex.lastIndex = 0; + let lastIndex = 0; + let match = regex.exec(segment.text); + let hasMatch = false; + + while (match) { + hasMatch = true; + if (match.index > lastIndex) { + nextSegments.push({ + text: segment.text.slice(lastIndex, match.index), + style: { ...segment.style } + }); + } + nextSegments.push({ + text: match[1], + style: { ...segment.style, ...style } + }); + lastIndex = match.index + match[0].length; + match = regex.exec(segment.text); + } + + if (!hasMatch) { + nextSegments.push(segment); + return; + } + + if (lastIndex < segment.text.length) { + nextSegments.push({ + text: segment.text.slice(lastIndex), + style: { ...segment.style } + }); + } + }); + segments = nextSegments; + }); + + return segments.filter((segment) => segment.text); + } + + parseMarkdownText(markdownText, fontFamily, fontSize, color, defaultBold = false, defaultItalic = false) { + const normalizedText = String(markdownText || '').replace(/\r\n/g, '\n'); + const lines = normalizedText.split('\n'); + const blocks = []; + + lines.forEach((rawLine) => { + const line = rawLine || ''; + const trimmed = line.trim(); + + if (!trimmed) { + blocks.push({ type: 'blank', height: Math.max(12, Math.round(fontSize * 0.75)) }); + return; + } + + let type = 'paragraph'; + let content = line; + let level = 0; + let indent = 0; + let prefix = ''; + + const headingMatch = trimmed.match(/^(#{1,6})\s+(.*)$/); + const quoteMatch = line.match(/^\s*>\s?(.*)$/); + const unorderedListMatch = line.match(/^(\s*)[-*+]\s+(.*)$/); + const orderedListMatch = line.match(/^(\s*)(\d+)\.\s+(.*)$/); + + if (headingMatch) { + type = 'heading'; + level = headingMatch[1].length; + content = headingMatch[2]; + } else if (quoteMatch) { + type = 'quote'; + content = quoteMatch[1]; + } else if (unorderedListMatch) { + type = 'list'; + content = unorderedListMatch[2]; + indent = Math.floor((unorderedListMatch[1] || '').length / 2); + prefix = '•'; + } else if (orderedListMatch) { + type = 'ordered-list'; + content = orderedListMatch[3]; + indent = Math.floor((orderedListMatch[1] || '').length / 2); + prefix = `${orderedListMatch[2]}.`; + } + + const scale = type === 'heading' ? Math.max(1, 1.65 - (level - 1) * 0.16) : 1; + const blockFontSize = Math.max(10, Math.round(fontSize * scale)); + const baseStyle = { + color: type === 'quote' ? '#444444' : color, + fontFamily, + fontSize: blockFontSize, + bold: type === 'heading' || defaultBold, + italic: defaultItalic + }; + + blocks.push({ + type, + level, + indent, + prefix, + fontSize: blockFontSize, + lineHeight: Math.max(14, Math.round(blockFontSize * (type === 'heading' ? 1.45 : 1.35))), + segments: this.parseInlineMarkdown(content, baseStyle) + }); + }); + + return blocks; + } + + measureMarkdownBlock(block) { + if (block.type === 'blank') { + return { width: 0, height: block.height || 12 }; + } + + const indentWidth = block.indent ? block.indent * 18 : 0; + let prefixWidth = 0; + if (block.prefix) { + this.ctx.font = this.buildFontString('Arial', block.fontSize, true, false); + prefixWidth = this.ctx.measureText(block.prefix).width + 8; + } + + let contentWidth = 0; + block.segments.forEach((segment) => { + this.ctx.font = this.buildFontString( + segment.style.fontFamily || 'Arial', + segment.style.fontSize || block.fontSize, + !!segment.style.bold, + !!segment.style.italic + ); + contentWidth += this.ctx.measureText(segment.text).width; + }); + + return { + width: indentWidth + prefixWidth + contentWidth + (block.type === 'quote' ? 16 : 0), + height: block.lineHeight + }; + } + + measureTextElement(textElement) { + if (textElement.type === 'markdown' && Array.isArray(textElement.blocks)) { + let width = 0; + let height = 0; + + textElement.blocks.forEach((block) => { + const size = this.measureMarkdownBlock(block); + width = Math.max(width, size.width); + height += size.height; + }); + + return { + width, + height, + top: textElement.y - Math.max(14, textElement.baseFontSize || 16), + left: textElement.x + }; + } + + this.ctx.font = textElement.font; + const textWidth = this.ctx.measureText(textElement.text).width; + const fontSizeMatch = textElement.font.match(/(\d+)px/); + const textHeight = (fontSizeMatch ? parseInt(fontSizeMatch[1], 10) : 14) * 1.2; + + return { + width: textWidth, + height: textHeight, + top: textElement.y - textHeight, + left: textElement.x + }; + } + + renderTextElement(textElement) { + if (textElement.type === 'markdown' && Array.isArray(textElement.blocks)) { + let currentY = textElement.y; + + textElement.blocks.forEach((block) => { + if (block.type === 'blank') { + currentY += block.height || 12; + return; + } + + let currentX = textElement.x + (block.indent ? block.indent * 18 : 0); + if (block.type === 'quote') { + this.ctx.strokeStyle = '#999999'; + this.ctx.lineWidth = 2; + this.ctx.beginPath(); + this.ctx.moveTo(textElement.x + 6, currentY - block.lineHeight + 4); + this.ctx.lineTo(textElement.x + 6, currentY + 2); + this.ctx.stroke(); + currentX += 16; + } + + if (block.prefix) { + this.ctx.font = this.buildFontString('Arial', block.fontSize, true, false); + this.ctx.fillStyle = textElement.color; + this.ctx.fillText(block.prefix, currentX, currentY); + currentX += this.ctx.measureText(block.prefix).width + 8; + } + + block.segments.forEach((segment) => { + const fontSize = segment.style.fontSize || block.fontSize; + this.ctx.font = this.buildFontString( + segment.style.fontFamily || textElement.fontFamily || 'Arial', + fontSize, + !!segment.style.bold, + !!segment.style.italic + ); + + const segmentWidth = this.ctx.measureText(segment.text).width; + + if (segment.style.code) { + this.ctx.fillStyle = segment.style.background || 'rgba(0, 0, 0, 0.08)'; + this.ctx.fillRect(currentX - 2, currentY - fontSize, segmentWidth + 4, block.lineHeight - 2); + } + + this.ctx.fillStyle = segment.style.color || textElement.color; + this.ctx.fillText(segment.text, currentX, currentY); + + if (segment.style.strike) { + this.ctx.strokeStyle = segment.style.color || textElement.color; + this.ctx.lineWidth = 1; + this.ctx.beginPath(); + this.ctx.moveTo(currentX, currentY - fontSize * 0.35); + this.ctx.lineTo(currentX + segmentWidth, currentY - fontSize * 0.35); + this.ctx.stroke(); + } + + currentX += segmentWidth; + }); + + currentY += block.lineHeight; + }); + + return; + } + + this.ctx.font = textElement.font; + this.ctx.fillStyle = textElement.color; + this.ctx.fillText(textElement.text, textElement.x, textElement.y); + } + saveToHistory() { // Remove any states after current step (when user drew something after undoing) this.historyStack = this.historyStack.slice(0, this.historyStep + 1); @@ -483,9 +749,7 @@ class PaintManager { } else { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } - this.ctx.font = this.selectedTextElement.font; - this.ctx.fillStyle = this.selectedTextElement.color; - this.ctx.fillText(this.selectedTextElement.text, this.selectedTextElement.x, this.selectedTextElement.y); + this.renderTextElement(this.selectedTextElement); } findTextElementAt(e) { @@ -499,21 +763,18 @@ class PaintManager { for (let i = this.textElements.length - 1; i >= 0; i--) { const text = this.textElements[i]; - // Calculate text dimensions - this.ctx.font = text.font; - const textWidth = this.ctx.measureText(text.text).width; - - // Extract font size correctly from the font string - const fontSizeMatch = text.font.match(/(\d+)px/); - const fontSize = fontSizeMatch ? parseInt(fontSizeMatch[1]) : 14; - const textHeight = fontSize * 1.2; // Approximate height + const measurement = this.measureTextElement(text); + const textWidth = measurement.width; + const textHeight = measurement.height; + const top = measurement.top; + const left = measurement.left; // Check if click is within text bounds (allowing for some margin) const margin = 5; - if (x >= text.x - margin && - x <= text.x + textWidth + margin && - y >= text.y - textHeight + margin && - y <= text.y + margin) { + if (x >= left - margin && + x <= left + textWidth + margin && + y >= top - margin && + y <= top + textHeight + margin) { return text; } } @@ -556,20 +817,20 @@ class PaintManager { const text = document.getElementById('text-input').value; const fontFamily = document.getElementById('font-family').value; - const fontSize = document.getElementById('font-size').value; - - // Build font style string - let fontStyle = ''; - if (this.textItalic) fontStyle += 'italic '; - if (this.textBold) fontStyle += 'bold '; + const fontSize = parseInt(document.getElementById('font-size').value, 10); + const blocks = this.parseMarkdownText(text, fontFamily, fontSize, this.brushColor, this.textBold, this.textItalic); // Create a new text element const newText = { + type: 'markdown', text: text, x: x, y: y, - font: `${fontStyle}${fontSize}px ${fontFamily}`, - color: this.brushColor + font: this.buildFontString(fontFamily, fontSize, this.textBold, this.textItalic), + color: this.brushColor, + fontFamily: fontFamily, + baseFontSize: fontSize, + blocks: blocks }; // Add to our list of text elements @@ -580,9 +841,7 @@ class PaintManager { this.draggingCanvasContext = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); // Draw text on canvas - this.ctx.font = newText.font; - this.ctx.fillStyle = newText.color; - this.ctx.fillText(newText.text, newText.x, newText.y); + this.renderTextElement(newText); // Save to history after placing text this.saveToHistory(); @@ -597,9 +856,7 @@ class PaintManager { redrawTextElements() { // Redraw all text elements after dithering this.textElements.forEach(item => { - this.ctx.font = item.font; - this.ctx.fillStyle = item.color; - this.ctx.fillText(item.text, item.x, item.y); + this.renderTextElement(item); }); }