# Hướng dẫn thực dụng về font trong Odoo

> Odoo xử lý font ở ba tầng tách biệt: backend, báo cáo PDF và website. Cần sửa file nào cho từng tầng, kèm một hướng code chạy được cho mỗi tầng.

**Date:** 2026-07-29
**Source:** <https://trobz.com/vi/insights/fonts-in-odoo/>

---



Bạn đã đổi biến SCSS, khởi động lại Odoo, mà PDF hóa đơn vẫn in ra font cũ. Đó không phải lỗi. Backend và báo cáo không dùng chung một luồng xử lý font, và cả hai cũng không dùng chung với website.

Các lời khuyên về font trong Odoo nằm rải rác trong tài liệu theme, hướng dẫn QWeb và các chủ đề trên diễn đàn, hiếm khi được ghi rõ là thuộc tầng nào. Bài viết này gom chúng lại. Đọc xong, bạn sẽ biết phải sửa file nào cho bề mặt nào, với một hướng code chạy được cho mỗi tầng và một danh sách ngắn các module dựng sẵn để khỏi phải viết code khi có thể.

Ví dụ code nhắm tới Odoo 17. Luồng xử lý báo cáo vẫn chạy trên wkhtmltopdf.

## Định dạng và họ font trong 60 giây

Nếu đã nắm các định dạng font, hãy bỏ qua mục này.

**Định dạng file**:

- **WOFF2** — lựa chọn mặc định cho web và cho asset của Odoo. Nén tốt nhất, mọi trình duyệt hiện đại đều hỗ trợ.
- **WOFF** — định dạng nén cũ hơn, giữ lại để dự phòng cho các trình duyệt rất cũ.
- **TTF / OTF** — định dạng không nén. Vẫn hữu ích vì một số bộ render PDF (đặc biệt là wkhtmltopdf) xử lý chúng ổn định hơn WOFF2.

Trong Odoo, thường bạn sẽ khai báo cả ba trong cùng một `@font-face` rồi để trình duyệt hoặc bộ render tự chọn.

**Họ font**: chọn theo chức năng, không theo thẩm mỹ. Odoo đã có sẵn mỗi vai trò một biến SCSS nên hiếm khi bạn cần nghĩ ra biến mới.

- **Sans-serif** — giao diện backend và phần chữ thân bài. Ví dụ: Roboto, Open Sans, Inter, Segoe UI.
- **Serif** — báo cáo in dạng dài, nơi người đọc quét qua nhiều dòng. Ví dụ: Merriweather, Source Serif, Noto Serif.
- **Monospace** — khối code và dữ liệu kỹ thuật. Ví dụ: Fira Code, JetBrains Mono, Source Code Pro.

## Font nằm ở đâu trong Odoo

Bản đồ tư duy, mỗi tầng một gạch đầu dòng.

**Web client backend**

- Biến SCSS trong `web/static/src/scss/primary_variables.scss`
- Asset bundle: `web.assets_backend`
- Ghi đè bằng cách định nghĩa lại biến trong SCSS của module bạn

**Báo cáo PDF**

- Khai báo `@font-face` trong `web/static/fonts/fonts.scss`
- Asset bundle: `web.report_assets_common`
- Ghi đè bằng cách thêm `@font-face` của riêng bạn trong một module rồi đẩy nó vào bundle báo cáo

**Website**

- Map SCSS `$o-theme-font-configs` trong `website/static/src/scss/secondary_variables.scss`
- Asset bundle: `web.assets_frontend` (từ Odoo 15.0 trở đi)
- Ghi đè bằng cách mở rộng map, thường là trong `primary_variables.scss` của một theme

Cứ xác định tầng trước, phần code sẽ tự hiện ra.

## Font ở backend

Ba biến SCSS điều khiển web client backend:

- `$o-system-fonts`: chuỗi font nền (Apple system, Segoe UI, Roboto, Helvetica Neue, v.v.)
- `$o-font-family-sans-serif`: chữ thân bài
- `$o-headings-font-family`: tiêu đề, mặc định là SF Pro Display

Một điểm tinh tế trước khi đụng vào chúng. Odoo bọc mọi họ font backend trong `o-add-unicode-support-font()`, hàm này xếp glyph Noto xuống lớp dưới để tiếng Việt, chữ Kirin và các hệ chữ không phải Latin vẫn hiển thị được khi font chính thiếu glyph. Hãy định nghĩa lại biến, đừng bỏ lớp bọc.

Một module font tùy chỉnh tối giản:

```
your_module/
├── __manifest__.py
└── static/
    ├── fonts/
    │   ├── CorporateSans.woff2
    │   ├── CorporateSans.woff
    │   └── CorporateSans.ttf
    └── src/scss/
        ├── fonts.scss
        └── variables.scss
```

`fonts.scss`:

```scss
@font-face {
  font-family: 'Corporate Sans';
  src: url('/your_module/static/fonts/CorporateSans.woff2') format('woff2'),
       url('/your_module/static/fonts/CorporateSans.woff')  format('woff'),
       url('/your_module/static/fonts/CorporateSans.ttf')   format('truetype');
  font-weight: 400;
  font-style: normal;
}
```

`variables.scss`:

```scss
$o-font-family-sans-serif: o-add-unicode-support-font(('Corporate Sans', sans-serif));
```

`__manifest__.py`:

```python
'assets': {
    'web.assets_backend': [
        'your_module/static/src/scss/fonts.scss',
        'your_module/static/src/scss/variables.scss',
    ],
},
```

Thứ tự có ý nghĩa: phần ghi đè biến phải nạp sau primary_variables lõi của Odoo thì định nghĩa mới của bạn mới thắng.

## Font trong báo cáo PDF

Bundle báo cáo tách riêng vì wkhtmltopdf render trong ngữ cảnh asset của chính nó. `web.assets_backend` không được nạp ở đó. Thay vào đó, mọi báo cáo QWeb đều gọi `<t t-call-assets="web.report_assets_common"/>`, và đó mới là bundle bạn cần mở rộng.

Cách làm giống hệt backend, chỉ hướng vào bundle báo cáo. Đưa `@font-face` của bạn vào một file SCSS trong module, nạp nó vào `web.report_assets_common` qua manifest, rồi tham chiếu họ font đó trong CSS báo cáo hoặc template QWeb.

```python
'assets': {
    'web.report_assets_common': [
        'your_module/static/src/scss/report_fonts.scss',
    ],
},
```

```scss
@font-face {
  font-family: 'Corporate Sans';
  src: url('/your_module/static/fonts/CorporateSans.ttf') format('truetype');
  font-weight: 400;
}

.o_report_layout, .header, .footer {
  font-family: 'Corporate Sans', sans-serif;
}
```

Với bundle báo cáo, hãy ưu tiên TTF. Một số bản build wkhtmltopdf xử lý WOFF2 không nhất quán, và việc nén cũng chẳng đem lại lợi ích gì khi render ở phía máy chủ.

**Glyph tiếng Việt và CJK**: wkhtmltopdf thay thế font một cách âm thầm khi thiếu glyph, nên một font trông đúng trên trình duyệt vẫn có thể rụng dấu trong PDF. Có hai lối ra: dùng một font có bộ glyph phủ đúng các ngôn ngữ của bạn, hoặc dựa vào họ `Odoo Unicode Support Noto` có sẵn của Odoo (đã được `web/static/fonts/fonts.scss` nạp) như một phương án dự phòng khai báo tường minh trong CSS.

## Font trên website

Luồng xử lý của website là thú vị nhất trong ba tầng, vì Odoo làm thay bạn nhiều việc hơn. Khi người biên tập site chọn một Google Font trong trình tùy biến theme, Odoo tải font đó về phía máy chủ, lưu các file WOFF2 thành bản ghi `ir.attachment` công khai, rồi viết lại các lệnh import SCSS để trỏ vào URL cục bộ. Kết quả là một font được host cục bộ, thân thiện với GDPR, mà không cần bước tải thủ công nào. Hành vi này được đưa vào ở [commit `b06ce21`](https://github.com/odoo/odoo/commit/b06ce21eba6388ce34bbffffadcb489f0e8557dd).

Map nằm ở trung tâm mọi thứ là `$o-theme-font-configs`. Mỗi mục có dạng:

```scss
$o-theme-font-configs: (
    'Corporate Sans': (
        'family': ('Corporate Sans', sans-serif),
        'url':    'Corporate+Sans:300,400,700',
        'name':   'Corporate Sans',
    ),
    ...
);
```

Giá trị `url` trỏ tới một chuỗi họ font của Google Fonts. Nếu người biên tập site lưu lại với font này đang được chọn, Odoo sẽ đổi `url` thành `attachment` và lưu font về máy. Muốn thêm font mới vào bộ chọn, hãy làm một theme (hoặc mở rộng theme có sẵn) và gộp các mục mới vào map trong `primary_variables.scss`. Cách làm trong `theme_avantgarde` là bản tham chiếu.

**Unicode**: cũng như ở backend, Odoo tự động áp `o-add-unicode-support-font()` cho mọi mục trong map. Bạn không cần tự thêm, và cũng không nên gỡ bỏ, nếu không các site tiếng Việt sẽ bắt đầu hiện ra ô vuông.

Nếu cần hẳn một giao diện cho người không phải lập trình viên tự tải font lên, thay vì bộ chọn Google Fonts, trình dựng WYSIWYG có hé lộ một phần khả năng này, nhưng luồng xử lý nằm ở tầng JavaScript và vượt ra ngoài phạm vi bài viết. Một bài tiếp theo sẽ nói về các module cộng đồng lấp khoảng trống này.

## Tóm lại

- Backend: định nghĩa lại một biến SCSS, nạp vào `web.assets_backend`.
- Báo cáo: thêm `@font-face`, nạp vào `web.report_assets_common`, tham chiếu trong CSS báo cáo.
- Website: mở rộng `$o-theme-font-configs` trong một theme, để Odoo lo phần tải về.

Chọn tầng đang chặn bạn hôm nay, chép đoạn code, đưa lên. Mô hình tư duy này sẽ nhớ được ngay từ lần đầu dùng thật. Font trong Odoo không khó, chúng chỉ là ba bài toán khoác lên mình bộ áo của một.

