> For the complete documentation index, see [llms.txt](https://408550179s-organization.gitbook.io/blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://408550179s-organization.gitbook.io/blog/leafer-zhong-jie-ru-quill-fu-wen-ben-bing-kuo-zhan-zi-ding-yi-ge-shi.md).

# Leafer中接入Quill富文本并扩展自定义格式

在画布编辑器里做文本功能，普通 `Text` 往往只能满足基础输入。一旦要支持局部字号、局部字体、行内描边、阴影、列表、对齐、HTML 内容保存，就需要一个真正的富文本编辑内核。

这个方案的核心思路是：

* 画布层继续由 Leafer UI 负责渲染和选中。
* 文本编辑状态下挂载一个 DOM 富文本编辑器。
* Quill 负责选区、输入、格式化。
* 编辑完成后把 HTML 和样式同步回画布节点。
* 需要的特殊样式通过 Quill 自定义 format 扩展。

## 为什么不用默认 format

Quill 默认支持 `bold`、`italic`、`size`、`color` 等能力，但是在画布编辑器里会遇到两个问题：

1. 默认字号有白名单，不能很好适配设计器里任意 px 的字号。
2. 一些设计器属性不是 Quill 默认格式，比如 `fontWeight`、`textStroke`、`textShadow`、局部字间距。

所以需要在初始化时注册一批自定义格式。

```ts
import Quill from "quill";

export const registerQuillCustomFormats = (): void => {
  const SizeStyle = Quill.import("attributors/style/size") as {
    scope: unknown;
    whitelist?: string[] | null;
    constructor: new (
      attrName: string,
      keyName: string,
      options: { scope: unknown },
    ) => unknown;
  };

  // 解除默认字号白名单，设计器里才能使用任意 px 字号
  SizeStyle.whitelist = null;
  Quill.register({ "attributors/style/size": SizeStyle, "formats/size": SizeStyle }, true);

  const StyleAttributorCtor = SizeStyle.constructor;

  const FontWeightAttributor = new StyleAttributorCtor("fontWeight", "font-weight", {
    scope: SizeStyle.scope,
  });

  Quill.register({
    "attributors/style/fontWeight": FontWeightAttributor,
    "formats/fontWeight": FontWeightAttributor,
  }, true);

  const TextStrokeAttributor = new StyleAttributorCtor("textStroke", "-webkit-text-stroke", {
    scope: SizeStyle.scope,
  });

  Quill.register({
    "attributors/style/textStroke": TextStrokeAttributor,
    "formats/textStroke": TextStrokeAttributor,
  }, true);

  const TextShadowAttributor = new StyleAttributorCtor("textShadow", "text-shadow", {
    scope: SizeStyle.scope,
  });

  Quill.register({
    "attributors/style/textShadow": TextShadowAttributor,
    "formats/textShadow": TextShadowAttributor,
  }, true);
};
```

## 全局属性和局部属性要分开

富文本里一个很容易踩坑的点是：全局字号、局部字号如果都写到 `font-size`，后面就很难区分到底是整段文本的属性，还是选区里的属性。

我的处理方式是单独注册局部 format，例如 `inlineFontSize`、`inlineFontFamily`、`inlineLetterSpacing`，这样可以避免全局属性和行内属性互相覆盖。

```ts
let inlineFontSizeRegistered = false;

export const registerInlineFontSizeFormat = (): void => {
  if (inlineFontSizeRegistered) return;

  const SizeStyle = Quill.import("attributors/style/size") as {
    scope: unknown;
    constructor: new (
      attrName: string,
      keyName: string,
      options: { scope: unknown },
    ) => unknown;
  };

  const StyleAttributorCtor = SizeStyle.constructor;

  const InlineFontSizeAttributor = new StyleAttributorCtor(
    "inlineFontSize",
    "font-size",
    { scope: SizeStyle.scope },
  );

  Quill.register({
    "attributors/style/inlineFontSize": InlineFontSizeAttributor,
    "formats/inlineFontSize": InlineFontSizeAttributor,
  }, true);

  inlineFontSizeRegistered = true;
};
```

## 统一入口处理格式化

在实际业务里，不建议每个按钮都直接操作 Quill。可以封装一个统一的 `setHTMLText` 方法：

```ts
export const setHTMLText = (key: string, value?: unknown, base64font?: string): void => {
  const quill = htmlTextManage.getQuill();
  const canvas = htmlTextManage.getCanvas();
  if (!quill || !canvas?.editor) return;

  const range = htmlTextManage.getSelection();
  const { editor } = canvas;

  htmlTextManage.dateEdit((leaf) => {
    const resolved = htmlTextManage.resolveHTMLTextLeaf(leaf);
    if (!resolved) return;

    const ctx = {
      quill,
      editor,
      leaf: resolved,
      range,
      value: value === 0 ? "0" : value,
      base64font,
    };

    const handler = FORMAT_HANDLERS[key];
    if (handler) {
      handler(ctx);
      return;
    }

    handleDefaultFormat(ctx, key);
  }, 1);
};
```

这样做有几个好处：

* 按钮、快捷键、右侧属性面板都能走同一套逻辑。
* 授权检测、选区处理、画布节点校验可以集中处理。
* 后续加新属性，只需要扩展 `FORMAT_HANDLERS`。

## 使用建议

如果是普通富文本页面，直接使用 Quill 默认能力就够了。但如果是画布编辑器，建议一开始就把“全局属性”和“局部属性”拆清楚。

尤其是字号、字体、字间距、描边这类样式，一旦混在一起，后面做缩放、复制、撤销重做、导入导出时都会变得很难维护。
