Bỏ qua, tới nội dung
Tài liệu APIv1

O3O DocBuilder và chuyển đổi

o3oscript cho văn bản

Mọi thao tác của kịch bản type = text: khổ giấy, kiểu chữ, đầu và chân trang, đoạn chữ định dạng, tiêu đề, đoạn văn, danh sách, bảng, ảnh và ngắt trang, mỗi thao tác có bảng tham số và ví dụ.

Trong trang này

Kịch bản type = "text" dựng một văn bản. Nội dung nằm trong mảng body (1 tới 20.000 khối) theo thứ tự xuất hiện; mỗi khối có khoá type là một trong heading, paragraph, list, table, image, page_break. Định dạng lưu: docx, odt, pdf.

Khổ giấy và lề: page#

Đối tượng page

  • sizeA3 | A4 | A5 | Letter | Legal | đối tượngtuỳ chọnMặc định: A4
    Khổ giấy có tên, hoặc {"width", "height"} tính bằng mm (50 tới 2000).
  • orientationportrait | landscapetuỳ chọnMặc định: portrait
    Hướng giấy.
  • marginobjecttuỳ chọnMặc định: 20 mm mỗi cạnh
    Lề trang.
  • margin.top / right / bottom / leftnumber 0–2000 (mm)tuỳ chọnMặc định: 20
    Lề từng cạnh.

Đối tượng style (kiểu chữ mặc định)

  • fontstringtuỳ chọn
    Font mặc định.
  • sizenumber 4–400 (pt)tuỳ chọn
    Cỡ chữ mặc định.
  • color#RRGGBBtuỳ chọn
    Màu chữ mặc định.
  • line_spacingnumber 50–300 (%)tuỳ chọn
    Giãn dòng; 100 là dòng đơn.
JSONKhổ A4, lề trái 30 mm, chữ có chân cỡ 13
{
  "page": {
    "size": "A4",
    "orientation": "portrait",
    "margin": {
      "top": 20,
      "right": 15,
      "bottom": 20,
      "left": 30
    }
  },
  "style": {
    "font": "Liberation Serif",
    "size": 13,
    "color": "#000000",
    "line_spacing": 120
  }
}

Đầu trang và chân trang là một dòng, áp cho mọi trang. content ghép từ chuỗi trơn, đối tượng run và trường tự cập nhật.

Đối tượng header hoặc footer

  • contentarray, 1–50 phần tửbắt buộc
    Mỗi phần tử là chuỗi (≤ 1000 ký tự), đối tượng run, hoặc trường {"field": ...}.
  • alignleft | center | righttuỳ chọnMặc định: center
    Căn lề.
  • sizenumber 4–400 (pt)tuỳ chọn
    Cỡ chữ.
  • color#RRGGBBtuỳ chọn
    Màu chữ.

Trường tự cập nhật

  • fieldpage_number | page_count | datebắt buộc
    page_number: số trang hiện tại; page_count: tổng số trang; date: ngày dựng tài liệu dạng dd/MM/yyyy.
JSONĐầu trang có tên công ty, chân trang đánh số trang
{
  "header": {
    "content": [
      {
        "text": "CÔNG TY CỔ PHẦN O3O",
        "bold": true
      },
      "  ·  Tài liệu nội bộ"
    ],
    "align": "left",
    "size": 9,
    "color": "#555555"
  },
  "footer": {
    "content": [
      "Trang ",
      {
        "field": "page_number"
      },
      "/",
      {
        "field": "page_count"
      },
      "  ·  Ngày in: ",
      {
        "field": "date"
      }
    ],
    "align": "right",
    "size": 9
  }
}

Đoạn chữ định dạng: run#

Nơi nào nhận runs (tiêu đề, đoạn văn, mục danh sách, ô bảng) và nội dung đầu, chân trang đều dùng cùng đối tượng run: một đoạn chữ liền nhau có chung định dạng. Khối có cả textruns thì runs được dùng.

Đối tượng run

  • textstring, ≤ 100.000bắt buộc
    Chữ của đoạn.
  • boldbooleantuỳ chọn
    Chữ đậm.
  • italicbooleantuỳ chọn
    Chữ nghiêng.
  • underlinebooleantuỳ chọn
    Gạch chân.
  • strikebooleantuỳ chọn
    Gạch ngang.
  • superscriptbooleantuỳ chọn
    Chỉ số trên.
  • subscriptbooleantuỳ chọn
    Chỉ số dưới.
  • color#RRGGBBtuỳ chọn
    Màu chữ.
  • background#RRGGBBtuỳ chọn
    Màu nền (tô sáng).
  • sizenumber 4–400 (pt)tuỳ chọn
    Cỡ chữ.
  • fontstringtuỳ chọn
    Tên font.
  • linkURL, ≤ 2048tuỳ chọn
    Biến đoạn chữ thành siêu liên kết.
JSONĐoạn văn trộn nhiều định dạng
{
  "type": "paragraph",
  "runs": [
    {
      "text": "Hạn nộp hồ sơ: "
    },
    {
      "text": "30/09/2026",
      "bold": true,
      "color": "#C00000"
    },
    {
      "text": ". Hướng dẫn tại "
    },
    {
      "text": "office.o3o.vn",
      "link": "https://office.o3o.vn",
      "underline": true,
      "color": "#1D55B8"
    },
    {
      "text": ". Ký hiệu hoá học của nước là H"
    },
    {
      "text": "2",
      "subscript": true
    },
    {
      "text": "O."
    }
  ]
}

Tiêu đề: heading#

Khối heading

  • type"heading"bắt buộc
    Loại khối.
  • levelinteger 1–6bắt buộc
    Cấp tiêu đề.
  • textstring, ≤ 2000tuỳ chọn
    Chữ trơn. Cần text hoặc runs.
  • runsarray run, 1–200tuỳ chọn
    Chữ có định dạng riêng.
  • alignleft | center | right | justifytuỳ chọn
    Căn lề.
  • color#RRGGBBtuỳ chọn
    Màu chữ.
  • page_break_beforebooleantuỳ chọnMặc định: false
    Sang trang mới trước tiêu đề.
JSONTiêu đề cấp 2
{
  "type": "heading",
  "level": 2,
  "text": "Điều 3. Giá trị hợp đồng",
  "color": "#1D55B8"
}

Tiêu đề ánh xạ sang kiểu đoạn Heading 1 tới Heading 6, nên xuất hiện trong ngăn điều hướng của trình soạn thảo và thành dấu trang khi xuất PDF.

Đoạn văn: paragraph#

Khối paragraph

  • type"paragraph"bắt buộc
    Loại khối.
  • textstring, ≤ 100.000tuỳ chọn
    Chữ trơn; ký tự xuống dòng \n thành ngắt dòng trong đoạn. Cần text hoặc runs.
  • runsarray run, 1–2000tuỳ chọn
    Chữ có định dạng riêng.
  • alignleft | center | right | justifytuỳ chọn
    Căn lề.
  • spacing_beforenumber (mm)tuỳ chọn
    Khoảng cách trước đoạn.
  • spacing_afternumber (mm)tuỳ chọn
    Khoảng cách sau đoạn.
  • line_spacingnumber 50–300 (%)tuỳ chọn
    Giãn dòng riêng của đoạn.
  • indent_leftnumber (mm)tuỳ chọn
    Thụt lề trái.
  • indent_rightnumber (mm)tuỳ chọn
    Thụt lề phải.
  • indent_firstnumber −200–200 (mm)tuỳ chọn
    Thụt dòng đầu; số âm là thụt treo.
  • keep_with_nextbooleantuỳ chọnMặc định: false
    Giữ đoạn cùng trang với đoạn sau.
  • page_break_beforebooleantuỳ chọnMặc định: false
    Sang trang mới trước đoạn.
JSONĐoạn căn đều, thụt dòng đầu 10 mm, có ngắt dòng
{
  "type": "paragraph",
  "text": "Căn cứ Bộ luật Dân sự năm 2015;\nCăn cứ nhu cầu và khả năng của hai bên.",
  "align": "justify",
  "indent_first": 10,
  "spacing_after": 3,
  "line_spacing": 130
}

Danh sách: list#

Khối list

  • type"list"bắt buộc
    Loại khối.
  • stylebullet | numbertuỳ chọnMặc định: bullet
    Gạch đầu dòng hoặc đánh số.
  • itemsarray, 1–5000 mụcbắt buộc
    Mỗi mục là chuỗi (≤ 10.000 ký tự) hoặc đối tượng mục.

Đối tượng mục danh sách

  • textstring, ≤ 10.000tuỳ chọn
    Chữ trơn. Cần text hoặc runs.
  • runsarray run, 1–200tuỳ chọn
    Chữ có định dạng riêng.
  • itemsarray, 1–1000 mụctuỳ chọn
    Danh sách con, cùng kiểu với danh sách cha.
JSONDanh sách đánh số có mục con
{
  "type": "list",
  "style": "number",
  "items": [
    "Bên A thanh toán trong 7 ngày làm việc.",
    {
      "text": "Bên B có trách nhiệm:",
      "items": [
        "Bàn giao token bản quyền",
        {
          "runs": [
            {
              "text": "Hỗ trợ kỹ thuật "
            },
            {
              "text": "trong giờ hành chính",
              "italic": true
            }
          ]
        }
      ]
    },
    "Hai bên cùng bảo mật thông tin."
  ]
}

Bảng: table#

Khối table

  • type"table"bắt buộc
    Loại khối.
  • columnsarray, 1–63 cộttuỳ chọn
    Mỗi cột {"width", "align"}: width là bề rộng tương đối 1 tới 1000 (mặc định 1).
  • headerarray ô, 1–63tuỳ chọn
    Hàng tiêu đề.
  • rowsarray hàng, 1–10.000bắt buộc
    Mỗi hàng là mảng 1 tới 63 ô.
  • borderfalse | đối tượng viềntuỳ chọn
    false: không kẻ; đối tượng: kẻ mọi đường, viền ngoài lẫn lưới trong.
  • header_fill#RRGGBBtuỳ chọn
    Màu nền hàng tiêu đề.
  • header_color#RRGGBBtuỳ chọn
    Màu chữ hàng tiêu đề.
  • header_boldbooleantuỳ chọnMặc định: true
    Chữ đậm ở hàng tiêu đề.
  • zebra_fill#RRGGBBtuỳ chọn
    Màu nền các hàng dữ liệu chẵn.
  • repeat_headerbooleantuỳ chọnMặc định: true
    Lặp hàng tiêu đề khi bảng sang trang.
  • width_percentnumber 10–100tuỳ chọnMặc định: 100
    Bề rộng bảng theo phần trăm vùng chữ.
  • alignleft | center | righttuỳ chọnMặc định: left
    Vị trí bảng khi width_percent nhỏ hơn 100.
  • sizenumber 4–400 (pt)tuỳ chọn
    Cỡ chữ trong bảng.
  • cell_paddingnumber 0–20 (mm)tuỳ chọnMặc định: 1.5
    Khoảng đệm trong ô.
  • spacing_afternumber (mm)tuỳ chọn
    Khoảng cách sau bảng.

Ô bảng dạng đối tượng

  • textstring, ≤ 20.000tuỳ chọn
    Chữ trơn. Cần text hoặc runs.
  • runsarray run, 1–200tuỳ chọn
    Chữ có định dạng riêng.
  • alignleft | center | right | justifytuỳ chọn
    Căn ngang.
  • valigntop | middle | bottomtuỳ chọn
    Căn dọc.
  • boldbooleantuỳ chọn
    Chữ đậm.
  • italicbooleantuỳ chọn
    Chữ nghiêng.
  • color#RRGGBBtuỳ chọn
    Màu chữ.
  • fill#RRGGBBtuỳ chọn
    Màu nền ô.

Đối tượng viền

  • widthnumber 0.25–6 (pt)tuỳ chọnMặc định: 0.5
    Độ dày nét.
  • color#RRGGBBtuỳ chọn
    Màu nét.
JSONBảng ba cột có hàng cộng
{
  "type": "table",
  "columns": [
    {
      "width": 1,
      "align": "center"
    },
    {
      "width": 5
    },
    {
      "width": 2,
      "align": "right"
    }
  ],
  "header": [
    "STT",
    "Hạng mục",
    "Số tiền (đồng)"
  ],
  "rows": [
    [
      1,
      "Thiết kế giao diện",
      "8.000.000"
    ],
    [
      2,
      "Lập trình và kiểm thử",
      "24.000.000"
    ],
    [
      {
        "text": "Cộng",
        "bold": true,
        "align": "right"
      },
      null,
      {
        "text": "32.000.000",
        "bold": true,
        "fill": "#EAF1FB"
      }
    ]
  ],
  "border": {
    "width": 0.5,
    "color": "#7F7F7F"
  },
  "header_fill": "#1D55B8",
  "header_color": "#FFFFFF",
  "zebra_fill": "#F7F9FC",
  "size": 11,
  "spacing_after": 4
}
Sắp có

Gộp ô trong bảng văn bản.

Ảnh: image#

Khối image

  • type"image"bắt buộc
    Loại khối.
  • srcobjectbắt buộc
    Nguồn ảnh: {"url"} hoặc {"base64", "mime"}.
  • widthnumber (mm)tuỳ chọn
    Bề rộng. Chỉ có một cạnh thì cạnh kia theo tỷ lệ gốc.
  • heightnumber (mm)tuỳ chọn
    Chiều cao.
  • alignleft | center | righttuỳ chọnMặc định: left
    Căn lề của ảnh.
  • captionstring, ≤ 1000tuỳ chọn
    Chú thích: một đoạn căn giữa, chữ nghiêng, ngay dưới ảnh.
  • altstring, ≤ 1000tuỳ chọn
    Chữ thay thế cho người dùng trình đọc màn hình.

Nguồn ảnh src

  • urlURL http/https, ≤ 2048tuỳ chọn
    DocBuilder tự tải ảnh, chịu quy tắc chống SSRF. Dùng url HOẶC base64.
  • base64stringtuỳ chọn
    Dữ liệu ảnh Base64 chuẩn, KHÔNG có tiền tố data:, tối đa 10 MB sau giải mã.
  • mimeimage/png | image/jpeg | image/gif | image/svg+xmltuỳ chọnMặc định: image/png
    Kiểu ảnh khi dùng base64.
JSONẢnh từ URL có chú thích
{
  "type": "image",
  "src": {
    "url": "https://example.com/images/so-do-to-chuc.png"
  },
  "width": 120,
  "align": "center",
  "caption": "Hình 1. Sơ đồ tổ chức",
  "alt": "Sơ đồ tổ chức công ty"
}
JSONẢnh nhúng base64
{
  "type": "image",
  "src": {
    "base64": "iVBORw0KGgoAAAANSUhEUgAAAKAAAABaCAIAAACwpMoFAAAAsUlEQVR42u3RMQ0AIAwAwapgIYhhZccMyhiRhoA6aC55BX8x9lXhwgLAAizAAizAAizAgAVYgAVYgAVYgAELsAALsAALsAALMGABFmABFmABVgJ+q6twgAELsAALsAALsAADFmABFmABFmABBizAAizAAizAAizAgAVYgAVYgAVYGbidqcIBBizAAizAAizAAgxYgAVYgAVYgAUYsAALsAALsAALsAADFmABFmABFmClPpawm2fxo+U6AAAAAElFTkSuQmCC",
    "mime": "image/png"
  },
  "height": 20,
  "alt": "Ba dải màu"
}

Ngắt trang: page_break#

JSONNgắt trang
{
  "type": "page_break"
}

Khối page_break chỉ có khoá type. Muốn một tiêu đề hay đoạn văn luôn bắt đầu ở trang mới, đặt page_break_before: true ngay trên khối đó.

Ví dụ đầy đủ: báo giá#

bao-gia.json dựng một báo giá hai trang: đầu trang, chân trang đánh số, tiêu đề, đoạn chữ định dạng, bảng có hàng cộng, danh sách lồng và ngắt trang. Kịch bản có 28 đơn vị.

JSONbao-gia.json
{
  "o3oscript": 1,
  "type": "text",
  "meta": {
    "title": "Báo giá O3O Office Online",
    "author": "Công ty Cổ phần O3O",
    "subject": "Báo giá bản quyền theo số kết nối",
    "keywords": [
      "báo giá",
      "O3O Office Online"
    ],
    "lang": "vi-VN"
  },
  "page": {
    "size": "A4",
    "orientation": "portrait",
    "margin": {
      "top": 20,
      "right": 20,
      "bottom": 20,
      "left": 25
    }
  },
  "style": {
    "font": "Liberation Sans",
    "size": 11,
    "line_spacing": 115
  },
  "header": {
    "content": [
      {
        "text": "O3O Office Online",
        "bold": true,
        "color": "#1D55B8"
      },
      "  ·  Báo giá số BG-2026-0917"
    ],
    "align": "left",
    "size": 9
  },
  "footer": {
    "content": [
      "Trang ",
      {
        "field": "page_number"
      },
      " / ",
      {
        "field": "page_count"
      }
    ],
    "align": "center",
    "size": 9,
    "color": "#666666"
  },
  "body": [
    {
      "type": "heading",
      "level": 1,
      "text": "BÁO GIÁ BẢN QUYỀN",
      "align": "center",
      "color": "#1D55B8"
    },
    {
      "type": "paragraph",
      "align": "center",
      "spacing_after": 6,
      "runs": [
        {
          "text": "Số: BG-2026-0917",
          "italic": true
        },
        {
          "text": "  ·  Ngày lập: 21/09/2026",
          "italic": true
        }
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "1. Thông tin khách hàng"
    },
    {
      "type": "paragraph",
      "runs": [
        {
          "text": "Kính gửi: ",
          "bold": true
        },
        {
          "text": "Công ty TNHH Thương mại và Dịch vụ An Phát"
        }
      ]
    },
    {
      "type": "paragraph",
      "spacing_after": 4,
      "runs": [
        {
          "text": "Địa chỉ: ",
          "bold": true
        },
        {
          "text": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội"
        }
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "2. Chi tiết báo giá"
    },
    {
      "type": "paragraph",
      "spacing_after": 3,
      "text": "Đơn giá áp cho toàn bộ số kết nối của đơn hàng. Một kết nối là một phiên soạn thảo đồng thời."
    },
    {
      "type": "table",
      "columns": [
        {
          "width": 1,
          "align": "center"
        },
        {
          "width": 6
        },
        {
          "width": 2,
          "align": "right"
        },
        {
          "width": 2,
          "align": "right"
        },
        {
          "width": 3,
          "align": "right"
        }
      ],
      "header": [
        "STT",
        "Hạng mục",
        "Số kết nối",
        "Đơn giá (USD)",
        "Thành tiền (USD)"
      ],
      "rows": [
        [
          1,
          "O3O Office Online, bản doanh nghiệp, thuê bao 1 năm",
          120,
          "5,00",
          "600,00"
        ],
        [
          2,
          "Hỗ trợ triển khai và đấu nối Nextcloud",
          null,
          null,
          "0,00"
        ],
        [
          {
            "text": "Tổng cộng",
            "bold": true,
            "align": "right"
          },
          null,
          null,
          null,
          {
            "text": "600,00",
            "bold": true,
            "fill": "#EAF1FB"
          }
        ]
      ],
      "border": {
        "width": 0.5,
        "color": "#7F7F7F"
      },
      "header_fill": "#1D55B8",
      "header_color": "#FFFFFF",
      "header_bold": true,
      "zebra_fill": "#F7F9FC",
      "repeat_header": true,
      "size": 10,
      "spacing_after": 4
    },
    {
      "type": "heading",
      "level": 2,
      "text": "3. Điều khoản"
    },
    {
      "type": "list",
      "style": "number",
      "items": [
        "Báo giá có hiệu lực 30 ngày kể từ ngày lập.",
        "Bản quyền được cấp dưới dạng token ký số, nạp vào máy chủ của khách hàng.",
        {
          "text": "Phạm vi của bản doanh nghiệp:",
          "items": [
            "Số kết nối đồng thời theo đơn hàng",
            "API DocBuilder đầy đủ",
            "Cập nhật trong suốt thời hạn thuê bao"
          ]
        }
      ]
    },
    {
      "type": "paragraph",
      "spacing_before": 8,
      "align": "right",
      "runs": [
        {
          "text": "ĐẠI DIỆN BÊN BÁN",
          "bold": true
        }
      ]
    },
    {
      "type": "paragraph",
      "align": "right",
      "runs": [
        {
          "text": "(Ký, ghi rõ họ tên và đóng dấu)",
          "italic": true,
          "size": 9
        }
      ]
    },
    {
      "type": "page_break"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Phụ lục: Cách đếm kết nối"
    },
    {
      "type": "list",
      "style": "bullet",
      "items": [
        "Một người mở ba tài liệu ở chế độ sửa được tính là ba kết nối.",
        "Phiên chỉ đọc và các yêu cầu tới DocBuilder không được tính.",
        "Khi chạm trần, phiên đang mở không bị ngắt và thao tác lưu không bị chặn."
      ]
    }
  ],
  "save": [
    {
      "format": "docx",
      "filename": "bao-gia.docx"
    }
  ]
}
Dựng báo giá ra docx
curl -sS http://localhost:8080/v1/build \
  -H "Authorization: Bearer O3O_DEMO_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @bao-gia.json \
  -o bao-gia.docx -w "HTTP %{http_code}\n"
import { readFile, writeFile } from "node:fs/promises";

const BASE_URL = "http://localhost:8080";
const HEADERS = { Authorization: "Bearer O3O_DEMO_KEY" };

const script = JSON.parse(await readFile("bao-gia.json", "utf8"));

const res = await fetch(`${BASE_URL}/v1/build`, {
  method: "POST",
  headers: { ...HEADERS, "Content-Type": "application/json" },
  body: JSON.stringify(script),
  signal: AbortSignal.timeout(90_000),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
await writeFile("bao-gia.docx", Buffer.from(await res.arrayBuffer()));
import json

import requests

BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}

with open("bao-gia.json", encoding="utf-8") as f:
    script = json.load(f)

r = requests.post(f"{BASE_URL}/v1/build", headers=HEADERS, json=script, timeout=90)
if not r.ok:
    raise RuntimeError(f"{r.status_code}: {r.text}")
with open("bao-gia.docx", "wb") as fh:
    fh.write(r.content)
<?php
$payload = file_get_contents("bao-gia.json");

$ch = curl_init("http://localhost:8080/v1/build");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ["Authorization: Bearer O3O_DEMO_KEY", "Content-Type: application/json"],
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 90,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($body === false || $status >= 400) {
    throw new RuntimeException("HTTP $status: " . ($body === false ? curl_error($ch) : $body));
}
file_put_contents("bao-gia.docx", $body);
using System.Net.Http.Headers;
using System.Text;

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(90) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "O3O_DEMO_KEY");

var json = await File.ReadAllTextAsync("bao-gia.json");
using var res = await http.PostAsync("http://localhost:8080/v1/build",
    new StringContent(json, Encoding.UTF8, "application/json"));
if (!res.IsSuccessStatusCode)
    throw new HttpRequestException($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
await File.WriteAllBytesAsync("bao-gia.docx", await res.Content.ReadAsByteArrayAsync());