feat: v0.10.1 —— PDF 里的代码块渲染成一整块带边框的面板
- 导出时不再逐行铺底色,改用 QTextFrame 把整段代码包在一个框里: 统一背景、1px 边框、内边距,跨页也保持连续 - 面板右上角标注语言名(从围栏 info string 取,cpp → C++,没有标记则不显示) - 行内代码颜色从强调红改为正文色,只保留灰底,跟编辑器一致 - 文档开头直接是代码块、以及无语言标记的情况都验证过 - 版本 0.10.1
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
一个轻量的跨平台 Markdown 编辑器,基于 **Qt 5 / C++17** 构建。
|
||||
|
||||
当前版本 **0.10.0**。
|
||||
当前版本 **0.10.1**。
|
||||
|
||||
## 界面
|
||||
|
||||
@@ -96,6 +96,9 @@ A4 纸、16mm 页边距。
|
||||
支持标题、列表(含任务列表)、引用、代码块、行内代码、粗体 / 斜体 / 删除线、
|
||||
链接;图片只输出 alt 文字,表格暂时按原文输出。
|
||||
|
||||
代码块在 PDF 里是**一整块带边框的面板**(不是一行一条底色),
|
||||
右上角标注语言名(C++ / Python …,取围栏上的标记)。
|
||||
|
||||
### 语言
|
||||
|
||||
界面支持中文和英文,在 **视图 → 语言** 里切换,立即生效、不需要重启。
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
TEMPLATE = app
|
||||
TARGET = VoiletMark
|
||||
VERSION = 0.10.0
|
||||
VERSION = 0.10.1
|
||||
|
||||
QT += core gui widgets printsupport
|
||||
|
||||
|
||||
+62
-18
@@ -1,10 +1,13 @@
|
||||
#include "MarkdownRenderer.h"
|
||||
|
||||
#include "CodeHighlight.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QRegularExpression>
|
||||
#include <QTextBlockFormat>
|
||||
#include <QTextCharFormat>
|
||||
#include <QTextFrame>
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocument>
|
||||
|
||||
@@ -37,8 +40,9 @@ struct Style
|
||||
QColor heading = hex("#11151a");
|
||||
QColor muted = hex("#5b6570");
|
||||
QColor rule = hex("#d0d7de");
|
||||
QColor codeText = hex("#b4295a");
|
||||
QColor codeText = hex("#24292f");
|
||||
QColor codeBg = hex("#f3f4f6");
|
||||
QColor border = hex("#d0d7de");
|
||||
QColor link = hex("#0969da");
|
||||
QColor marker = hex("#0969da");
|
||||
};
|
||||
@@ -62,7 +66,7 @@ private:
|
||||
void renderHeading(int level, const QString &text);
|
||||
void renderParagraph(const QString &text, int quoteDepth);
|
||||
void renderListItem(const QString &marker, const QString &text, int indentLevel);
|
||||
void renderCodeBlock(const QStringList &lines);
|
||||
void renderCodeBlock(const QStringList &lines, const QString &info);
|
||||
void renderRule();
|
||||
void renderInline(const QString &text, const QTextCharFormat &base);
|
||||
|
||||
@@ -158,29 +162,69 @@ void Renderer::renderListItem(const QString &marker, const QString &text, int in
|
||||
renderInline(text, bodyFormat());
|
||||
}
|
||||
|
||||
void Renderer::renderCodeBlock(const QStringList &lines)
|
||||
void Renderer::renderCodeBlock(const QStringList &lines, const QString &info)
|
||||
{
|
||||
QTextBlockFormat blockFormat;
|
||||
blockFormat.setLineHeight(135, QTextBlockFormat::ProportionalHeight);
|
||||
blockFormat.setTopMargin(6);
|
||||
blockFormat.setBottomMargin(10);
|
||||
blockFormat.setLeftMargin(14);
|
||||
blockFormat.setRightMargin(14);
|
||||
blockFormat.setBackground(m_style.codeBg);
|
||||
if (m_firstBlock) {
|
||||
// 文档一开头就是代码块:先垫一个空段落,frame 才有地方插。
|
||||
QTextBlockFormat emptyFormat;
|
||||
beginBlock(emptyFormat, bodyFormat());
|
||||
}
|
||||
|
||||
QTextFrameFormat frameFormat;
|
||||
frameFormat.setBackground(m_style.codeBg);
|
||||
frameFormat.setBorder(1);
|
||||
frameFormat.setBorderBrush(m_style.border);
|
||||
frameFormat.setBorderStyle(QTextFrameFormat::BorderStyle_Solid);
|
||||
frameFormat.setPadding(8);
|
||||
frameFormat.setTopMargin(6);
|
||||
frameFormat.setBottomMargin(12);
|
||||
|
||||
QTextFrame *frame = m_cursor.insertFrame(frameFormat);
|
||||
QTextCursor frameCursor(m_document);
|
||||
frameCursor.setPosition(frame->firstPosition());
|
||||
bool firstInside = true;
|
||||
|
||||
const auto insideBlock = [&](const QTextBlockFormat &blockFormat,
|
||||
const QTextCharFormat &charFormat) {
|
||||
if (firstInside) {
|
||||
frameCursor.setBlockFormat(blockFormat);
|
||||
frameCursor.setBlockCharFormat(charFormat);
|
||||
firstInside = false;
|
||||
} else {
|
||||
frameCursor.insertBlock(blockFormat, charFormat);
|
||||
}
|
||||
};
|
||||
|
||||
const QString language = CodeHighlight::displayName(CodeHighlight::fromInfoString(info));
|
||||
if (!language.isEmpty()) {
|
||||
QTextBlockFormat labelFormat;
|
||||
labelFormat.setAlignment(Qt::AlignRight);
|
||||
labelFormat.setBottomMargin(2);
|
||||
|
||||
QTextCharFormat labelChar;
|
||||
labelChar.setForeground(m_style.muted);
|
||||
labelChar.setFontPointSize(kBodySize * 0.8);
|
||||
|
||||
insideBlock(labelFormat, labelChar);
|
||||
frameCursor.insertText(language, labelChar);
|
||||
}
|
||||
|
||||
QTextBlockFormat codeBlock;
|
||||
codeBlock.setLineHeight(130, QTextBlockFormat::ProportionalHeight);
|
||||
const QTextCharFormat format = codeFormat();
|
||||
|
||||
QStringList content = lines;
|
||||
if (content.isEmpty())
|
||||
content << QString();
|
||||
|
||||
for (int index = 0; index < content.size(); ++index) {
|
||||
QTextBlockFormat lineFormat = blockFormat;
|
||||
lineFormat.setTopMargin(index == 0 ? 6 : 0);
|
||||
lineFormat.setBottomMargin(index == content.size() - 1 ? 10 : 0);
|
||||
beginBlock(lineFormat, format);
|
||||
const QString line = content.at(index);
|
||||
m_cursor.insertText(line.isEmpty() ? QStringLiteral(" ") : line, format);
|
||||
for (const QString &line : content) {
|
||||
insideBlock(codeBlock, format);
|
||||
frameCursor.insertText(line.isEmpty() ? QStringLiteral(" ") : line, format);
|
||||
}
|
||||
|
||||
// 光标挪到边框外面,后面的内容接着写
|
||||
m_cursor = QTextCursor(m_document);
|
||||
m_cursor.setPosition(frame->lastPosition());
|
||||
}
|
||||
|
||||
void Renderer::renderRule()
|
||||
@@ -329,7 +373,7 @@ void Renderer::run(const QString &markdown)
|
||||
code << lines.at(index++);
|
||||
if (index < lines.size())
|
||||
++index; // 跳过闭合围栏
|
||||
renderCodeBlock(code);
|
||||
renderCodeBlock(code, fence.captured(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user