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** 构建。
|
一个轻量的跨平台 Markdown 编辑器,基于 **Qt 5 / C++17** 构建。
|
||||||
|
|
||||||
当前版本 **0.10.0**。
|
当前版本 **0.10.1**。
|
||||||
|
|
||||||
## 界面
|
## 界面
|
||||||
|
|
||||||
@@ -96,6 +96,9 @@ A4 纸、16mm 页边距。
|
|||||||
支持标题、列表(含任务列表)、引用、代码块、行内代码、粗体 / 斜体 / 删除线、
|
支持标题、列表(含任务列表)、引用、代码块、行内代码、粗体 / 斜体 / 删除线、
|
||||||
链接;图片只输出 alt 文字,表格暂时按原文输出。
|
链接;图片只输出 alt 文字,表格暂时按原文输出。
|
||||||
|
|
||||||
|
代码块在 PDF 里是**一整块带边框的面板**(不是一行一条底色),
|
||||||
|
右上角标注语言名(C++ / Python …,取围栏上的标记)。
|
||||||
|
|
||||||
### 语言
|
### 语言
|
||||||
|
|
||||||
界面支持中文和英文,在 **视图 → 语言** 里切换,立即生效、不需要重启。
|
界面支持中文和英文,在 **视图 → 语言** 里切换,立即生效、不需要重启。
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
TEMPLATE = app
|
TEMPLATE = app
|
||||||
TARGET = VoiletMark
|
TARGET = VoiletMark
|
||||||
VERSION = 0.10.0
|
VERSION = 0.10.1
|
||||||
|
|
||||||
QT += core gui widgets printsupport
|
QT += core gui widgets printsupport
|
||||||
|
|
||||||
|
|||||||
+62
-18
@@ -1,10 +1,13 @@
|
|||||||
#include "MarkdownRenderer.h"
|
#include "MarkdownRenderer.h"
|
||||||
|
|
||||||
|
#include "CodeHighlight.h"
|
||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QTextBlockFormat>
|
#include <QTextBlockFormat>
|
||||||
#include <QTextCharFormat>
|
#include <QTextCharFormat>
|
||||||
|
#include <QTextFrame>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
|
|
||||||
@@ -37,8 +40,9 @@ struct Style
|
|||||||
QColor heading = hex("#11151a");
|
QColor heading = hex("#11151a");
|
||||||
QColor muted = hex("#5b6570");
|
QColor muted = hex("#5b6570");
|
||||||
QColor rule = hex("#d0d7de");
|
QColor rule = hex("#d0d7de");
|
||||||
QColor codeText = hex("#b4295a");
|
QColor codeText = hex("#24292f");
|
||||||
QColor codeBg = hex("#f3f4f6");
|
QColor codeBg = hex("#f3f4f6");
|
||||||
|
QColor border = hex("#d0d7de");
|
||||||
QColor link = hex("#0969da");
|
QColor link = hex("#0969da");
|
||||||
QColor marker = hex("#0969da");
|
QColor marker = hex("#0969da");
|
||||||
};
|
};
|
||||||
@@ -62,7 +66,7 @@ private:
|
|||||||
void renderHeading(int level, const QString &text);
|
void renderHeading(int level, const QString &text);
|
||||||
void renderParagraph(const QString &text, int quoteDepth);
|
void renderParagraph(const QString &text, int quoteDepth);
|
||||||
void renderListItem(const QString &marker, const QString &text, int indentLevel);
|
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 renderRule();
|
||||||
void renderInline(const QString &text, const QTextCharFormat &base);
|
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());
|
renderInline(text, bodyFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::renderCodeBlock(const QStringList &lines)
|
void Renderer::renderCodeBlock(const QStringList &lines, const QString &info)
|
||||||
{
|
{
|
||||||
QTextBlockFormat blockFormat;
|
if (m_firstBlock) {
|
||||||
blockFormat.setLineHeight(135, QTextBlockFormat::ProportionalHeight);
|
// 文档一开头就是代码块:先垫一个空段落,frame 才有地方插。
|
||||||
blockFormat.setTopMargin(6);
|
QTextBlockFormat emptyFormat;
|
||||||
blockFormat.setBottomMargin(10);
|
beginBlock(emptyFormat, bodyFormat());
|
||||||
blockFormat.setLeftMargin(14);
|
}
|
||||||
blockFormat.setRightMargin(14);
|
|
||||||
blockFormat.setBackground(m_style.codeBg);
|
|
||||||
|
|
||||||
|
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();
|
const QTextCharFormat format = codeFormat();
|
||||||
|
|
||||||
QStringList content = lines;
|
QStringList content = lines;
|
||||||
if (content.isEmpty())
|
if (content.isEmpty())
|
||||||
content << QString();
|
content << QString();
|
||||||
|
|
||||||
for (int index = 0; index < content.size(); ++index) {
|
for (const QString &line : content) {
|
||||||
QTextBlockFormat lineFormat = blockFormat;
|
insideBlock(codeBlock, format);
|
||||||
lineFormat.setTopMargin(index == 0 ? 6 : 0);
|
frameCursor.insertText(line.isEmpty() ? QStringLiteral(" ") : line, format);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 光标挪到边框外面,后面的内容接着写
|
||||||
|
m_cursor = QTextCursor(m_document);
|
||||||
|
m_cursor.setPosition(frame->lastPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::renderRule()
|
void Renderer::renderRule()
|
||||||
@@ -329,7 +373,7 @@ void Renderer::run(const QString &markdown)
|
|||||||
code << lines.at(index++);
|
code << lines.at(index++);
|
||||||
if (index < lines.size())
|
if (index < lines.size())
|
||||||
++index; // 跳过闭合围栏
|
++index; // 跳过闭合围栏
|
||||||
renderCodeBlock(code);
|
renderCodeBlock(code, fence.captured(1));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user