← Quay lại blog

Tạo PDF trực tiếp trên trình duyệt: Kết xuất Canvas, Đồ họa vector và Trình biên dịch WASM

PetexSpace

Việc tạo các tệp Portable Document Format (PDF) đạt chuẩn production trước đây thường được giao cho các cụm server backend chạy các phiên bản Chromium không giao diện (headless) hoặc các thư viện C gốc. Khi các trình duyệt web phát triển thành các môi trường runtime ứng dụng mạnh mẽ, các quy trình xử lý tài liệu ngày càng chuyển dịch sang tạo trực tiếp các tài liệu PDF an toàn, có phân trang và độ trung thực cao ngay trên thiết bị của người dùng.

Tuy nhiên, kiến trúc tạo PDF trong trình duyệt vẫn còn phân mảnh. Các lập trình viên thường mặc định sử dụng các thủ thuật rasterization DOM nhanh chóng, điều này làm giảm khả năng tiếp cận (accessibility) của tài liệu, làm phình to kích thước tệp và khiến văn bản bị mờ khi phóng to.

Để đạt được độ sắc nét chuẩn in ấn thực sự, ta cần hiểu rõ đặc tả ISO 32000-1, xây dựng các luồng đồ họa vector cấp thấp, trích xuất tập con (subsetting) phông chữ TrueType ngay trong bộ nhớ và chạy các engine tài liệu native được biên dịch sang WebAssembly trực tiếp bên trong trình duyệt client.

1. Phân tích đặc tả PDF/A và Mô hình đối tượng cấp thấp

Portable Document Format không phải là một ngôn ngữ đánh dấu hay một cây tài liệu có cấu trúc như HTML hay XML. Bắt nguồn từ PostScript, PDF là một định dạng tệp nhị phân có đánh chỉ mục, được thiết kế để mô tả chính xác vị trí hiển thị trực quan của văn bản, các hình khối vector và hình ảnh raster trên một khung vẽ 2D cố định, độc lập với thiết bị đầu ra.

+-----------------------------------------------------------------------+
|                    ISO 32000-1 Physical File Layout                   |
+-----------------------------------------------------------------------+
|  %PDF-1.7                                 <-- Header Magic Bytes      |
|  %âãÏÓ                                    <-- Binary Marker (>127)    |
|                                                                       |
|  1 0 obj                                  <-- Object 1: Catalog       |
|  << /Type /Catalog /Pages 2 0 R >>                                    |
|  endobj                                                               |
|                                                                       |
|  2 0 obj                                  <-- Object 2: Pages Tree    |
|  << /Type /Pages /Kids [3 0 R] /Count 1 >>                            |
|  endobj                                                               |
|                                                                       |
|  3 0 obj                                  <-- Object 3: Page Node     |
|  << /Type /Page /Parent 2 0 R                                         |
|     /MediaBox [0 0 612 792]                                           |
|     /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>         |
|  endobj                                                               |
|                                                                       |
|  4 0 obj                                  <-- Object 4: Stream Data   |
|  << /Length 78 /Filter /FlateDecode >>                                |
|  stream                                                               |
|  ... [Deflate-compressed vector and text operator bytes] ...          |
|  endstream                                                            |
|  endobj                                                               |
|                                                                       |
|  xref                                     <-- Cross-Reference Table   |
|  0 5                                                                  |
|  0000000000 65535 f                                                   |
|  0000000015 00000 n                                                   |
|  0000000074 00000 n                                                   |
|  0000000135 00000 n                                                   |
|  0000000310 00000 n                                                   |
|                                                                       |
|  trailer                                  <-- Trailer Dictionary      |
|  << /Size 5 /Root 1 0 R >>                                            |
|  startxref                                                            |
|  450                                      <-- Byte Offset to xref     |
|  %%EOF                                    <-- End-of-File Sentinel    |
+-----------------------------------------------------------------------+

Bảy kiểu dữ liệu nguyên thủy cơ bản của PDF

Mọi tài liệu PDF đều được xây dựng từ bảy kiểu dữ liệu nền tảng:

  1. Booleans: true và false.
  2. Numbers: Số nguyên (ví dụ: 42) hoặc số thực dấu phẩy động (ví dụ: 3.14159).
  3. Strings: Được đặt trong dấu ngoặc đơn đối với chuỗi ký tự ASCII/PDFDocEncoding ((Sample Text)) hoặc trong ngoặc nhọn đối với biểu diễn thập lục phân (<48656C6C6F>).
  4. Names: Các định danh nguyên tử bắt đầu bằng dấu gạch chéo xuôi (ví dụ: /Type, /Font, /MediaBox).
  5. Arrays: Tập hợp có thứ tự của các đối tượng được bao bọc trong dấu ngoặc vuông (ví dụ: [0 0 612 792]).
  6. Dictionaries: Bảng tra cứu khóa-giá trị được bao bọc trong dấu ngoặc nhọn kép (<< /Key /Value >>), trong đó các khóa bắt buộc phải là Name.
  7. Streams: Các chuỗi byte nhị phân tùy ý nằm giữa từ khóa stream và endstream, đi kèm với một từ điển chỉ định độ dài và bộ lọc nén (/FlateDecode).

Đồ thị tài liệu và Bảng tham chiếu chéo (Cross-Reference Table)

Một tệp PDF hợp lệ tạo thành một đồ thị có hướng không chu trình (DAG) bắt nguồn từ từ điển Catalog (/Catalog). Catalog trỏ đến gốc của cây /Pages, phân nhánh xuống các nút lá /Page riêng lẻ. Mỗi trang chỉ định kích thước bao quanh (/MediaBox), các tài nguyên liên quan (/Resources, chẳng hạn như phông chữ, hình ảnh và không gian màu) cùng một hoặc nhiều luồng /Contents chứa các lệnh vẽ.

Điểm quan trọng là các trình phân tích cú pháp PDF không đọc tệp tuần tự từ trên xuống dưới. Thay vào đó, trình phân tích cú pháp bắt đầu từ phần cuối vật lý của tệp, xác định vị trí điểm đánh dấu %%EOF, đọc độ lệch byte startxref và kiểm tra Bảng tham chiếu chéo (xref). Bảng xref chỉ định vị trí byte chính xác của mọi đối tượng gián tiếp (obj ... endobj) trong tệp.

Kiến trúc này hỗ trợ cơ chế kết xuất truy cập ngẫu nhiên, cho phép trình xem tải và hiển thị trang 250 mà không cần đọc hoặc phân tích cú pháp 249 trang trước đó.

2. Rasterization DOM so với Tạo Vector nguyên bản: Cuộc chiến về độ sắc nét

Khi các nhóm sản phẩm triển khai tính năng xuất PDF trong các ứng dụng web, họ thường phải chọn giữa hai hướng tiếp cận hoàn toàn trái ngược nhau: rasterization DOM hoặc tạo vector nguyên bản.

+-----------------------------------------------------------------------+
|             DOM Rasterization vs. Native Vector Assembly              |
+-----------------------------------------------------------------------+
| Approach A: DOM Rasterization (html2canvas, window.print)             |
|   DOM HTML Elements                                                   |
|         |                                                             |
|         v                                                             |
|   [ Canvas Rasterization Engine ] ---> Converts text & CSS to pixels  |
|         |                                                             |
|         v                                                             |
|   [ JPEG/PNG Compression ] ----------> Injected into single PDF image |
|                                                                       |
|   Drawbacks: 72-96 DPI blur, unselectable text, no hyperlinks, massive|
|              file size, non-conforming PDF/A accessibility.           |
+-----------------------------------------------------------------------+
| Approach B: Vector-Native Generation (PetexSpace Architecture)        |
|   Document Abstract Syntax Tree                                       |
|         |                                                             |
|         v                                                             |
|   [ In-Memory PDF Writer ]                                            |
|   - Subsets TrueType font glyphs (.ttf -> .cff/.ttf subset)          |
|   - Emits compact text operators: BT /F1 12 Tf 72 712 Td (Text) Tj ET |
|   - Emits mathematical vector paths: 100 100 m 200 200 l S            |
|         |                                                             |
|         v                                                             |
|   Crisp, infinitely scalable, selectable, searchable, lightweight.    |
+-----------------------------------------------------------------------+

Những hạn chế của Canvas và Rasterization DOM

Quy trình rasterization (thường sử dụng html2canvas kết hợp với các thư viện bao bọc đơn giản) tuần tự hóa DOM thực tế, vẽ nó lên một <canvas> HTML5 và đóng gói hình ảnh raster thu được bên trong một vỏ bọc tài liệu PDF tối giản.

Dù dễ triển khai, mô hình này bộc lộ những nhược điểm kiến trúc nghiêm trọng:

  • Suy giảm độ phân giải: Màn hình tiêu chuẩn hiển thị ở độ phân giải 72 đến 96 DPI. Việc in ấn đòi hỏi tối thiểu từ 300 đến 600 DPI. Việc nâng độ phân giải canvas ngốn lượng bộ nhớ khổng lồ của trình duyệt nhưng văn bản vẫn bị mờ rõ rệt.
  • Mất ngữ nghĩa của văn bản: Vì các ký tự bị biến thành các pixel tĩnh, người đọc không thể tìm kiếm từ khóa, bôi đen câu, sao chép văn bản hoặc điều hướng bằng trình đọc màn hình.
  • Mất siêu dữ liệu và siêu liên kết: Các siêu liên kết, cấu trúc dàn trang, bookmark mục lục và siêu dữ liệu tài liệu đều bị loại bỏ.
  • Kích thước tệp phình to: Một tài liệu rasterized 10 trang thường có dung lượng từ 25 đến 50 megabyte, trong khi phiên bản tương đương dạng vector nguyên bản chứa các phông chữ nhúng thường chỉ dưới 400 kilobyte.

Các toán tử vẽ vector: Xuất trực tiếp chỉ thị PDF

Ngược lại, việc tạo PDF dạng vector nguyên bản xây dựng luồng nội dung bằng cách xuất các toán tử vẽ cấp thấp bắt nguồn từ PostScript:

BT                          % Begin Text Object
/F1 12 Tf                   % Set Font /F1 at 12pt size
1 0 0 1 72 720 Tm           % Set Text Matrix: Translate to x=72, y=720
(Hello, PetexSpace!) Tj     % Show Text String
ET                          % End Text Object

0.2 0.4 0.8 rg              % Set RGB fill color (Non-stroking)
100 600 200 150 re          % Define rectangle at x=100, y=600, w=200, h=150
f                           % Fill rectangle path

Do văn bản được phát ra dưới dạng mã ký tự tham chiếu đến các đường viền glyph vector, tài liệu duy trì khả năng phóng to thu nhỏ vô hạn, có thể tìm kiếm toàn diện, hỗ trợ trình đọc màn hình và có dung lượng lưu trữ nhỏ hơn nhiều lần.

3. Nghệ thuật chữ, Trích xuất tập con phông chữ TrueType và CMap Unicode

Thách thức kỹ thuật phức tạp nhất khi xây dựng một công cụ tạo PDF vector trong trình duyệt là quản lý phông chữ. Một tệp phông chữ TrueType (.ttf) hoặc OpenType (.otf) tiêu chuẩn cho một bộ phông chữ đầy đủ (chẳng hạn như Noto Sans hoặc Roboto) thường vượt quá 20 megabyte khi hỗ trợ các hệ chữ viết Unicode quốc tế.

Việc nhúng toàn bộ tệp phông chữ vào mỗi tài liệu được tạo ra sẽ khiến kích thước tệp tăng vọt không thể chấp nhận. Công cụ tạo tài liệu phải thực hiện trích xuất tập con phông chữ (subsetting) theo thời gian thực ngay trong bộ nhớ trình duyệt.

+-----------------------------------------------------------------------+
|                 In-Memory Font Subsetting Pipeline                    |
+-----------------------------------------------------------------------+
|  Original TrueType Font (.ttf) [~25 MB, 15,000 glyphs]                |
|       |                                                               |
|       v                                                               |
|  [ Ingest Document Text Stream ] ---> Identify unique characters      |
|                                       Used: { 'P', 'e', 't', 'x', ... }|
|       |                                                               |
|       v                                                               |
|  [ Glyph Harvester & SFNT Builder ]                                   |
|    - Parse SFNT tables: 'head', 'hhea', 'maxp', 'glyf', 'loca'        |
|    - Extract only required glyph outlines and composite dependencies   |
|    - Renumber glyph IDs starting from 0 to N                          |
|    - Recalculate checksums & table offsets                            |
|       |                                                               |
|       v                                                               |
|  [ Output SubsetFont.ttf ] [~18 KB, 42 glyphs]                        |
|       |                                                               |
|       +--> Embed into /FontFile2 Stream                               |
|       +--> Synthesize /ToUnicode CMap for reverse text search         |
+-----------------------------------------------------------------------+

Phân tích cấu trúc SFNT

Các phông chữ TrueType được đóng gói bên trong một container SFNT bao gồm một thư mục bảng theo sau là các bảng chuyên dụng:

  • head: Tiêu đề phông chữ chứa siêu dữ liệu toàn cục, giới hạn khung bao và cờ indexToLocFormat.
  • hhea và hmtx: Tiêu đề ngang và số liệu ngang xác định độ rộng khoảng tiến ký tự và lề hai bên.
  • maxp: Hồ sơ tối đa chỉ định tổng số lượng glyph trong phông chữ.
  • loca: Bảng vị trí lưu trữ độ lệch byte đến dữ liệu glyph riêng lẻ bên trong bảng glyf.
  • glyf: Dữ liệu đường cong vector thô (đường cong Bézier và tọa độ) cho mỗi glyph ký tự.
  • cmap: Bảng ánh xạ ký tự sang glyph chuyển đổi các điểm mã (code point) Unicode thành Glyph ID (GID) nội bộ của phông chữ.

Xây dựng SubsetFont bằng TypeScript

Engine trích xuất tập con kiểm tra từng ký tự hiển thị trong tài liệu, giải quyết GID tương ứng thông qua cmap của phông chữ và xây dựng một tệp nhị phân SFNT tối giản mới trong bộ nhớ ảo chỉ chứa các glyph được tham chiếu đó:

// Subsetting helper extracting glyph dependencies
export class FontSubsetter {
  private usedGlyphIds = new Set<number>([0]); // Always preserve GID 0 (.notdef)

  public markGlyphUsed(unicodeChar: string, cmapTable: Map<number, number>): void {
    const codePoint = unicodeChar.codePointAt(0);
    if (codePoint !== undefined) {
      const gid = cmapTable.get(codePoint) ?? 0;
      this.usedGlyphIds.add(gid);
    }
  }

  public assembleSubsetFont(originalFontBytes: Uint8Array): Uint8Array {
    // 1. Traverse 'glyf' table to collect composite sub-glyphs
    // 2. Build new sequential glyph index mapping (0 .. N-1)
    // 3. Rebuild 'loca', 'glyf', 'hmtx', and 'maxp' tables
    // 4. Recalculate SFNT table directory and 4-byte checksums
    return new Uint8Array(); // Returns optimized binary font slice
  }
}

Vai trò của CMap /ToUnicode

Khi một trình xem PDF hiển thị văn bản, nó kết xuất các chỉ số glyph được ánh xạ theo số liệu phông chữ. Tuy nhiên, khi người dùng sao chép văn bản vào clipboard hoặc khi công cụ lập chỉ mục thu thập dữ liệu tài liệu, trình xem phải ánh xạ các chỉ số glyph tùy ý đó trở lại các ký tự Unicode chuẩn.

Ánh xạ này được điều khiển bởi một luồng CMap /ToUnicode được nhúng kèm. Nếu không có ánh xạ /ToUnicode hợp lệ, văn bản sao chép sẽ biến thành các ký tự vô nghĩa:

/CIDInit /ProcSet findresource begin
12 dict begin
begincmap
/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def
/CMapName /Custom-ToUnicode def
/CMapType 2 def
1 begincodespacerange
<0001> <00FF>
endcodespacerange
1 beginbfrange
<0001> <0001> <0050> % Maps Glyph 1 to Unicode 'P' (U+0050)
endbfrange
endcmap
CMapName currentdict /CMap defineresource pop
end
end

4. Các engine lắp ráp PDF tăng tốc bằng WebAssembly

Mặc dù việc tạo các tệp PDF cơ bản bằng các thư viện JavaScript thuần là khả thi đối với các biểu mẫu đơn giản, kỹ thuật xử lý tài liệu nâng cao đòi hỏi các layout engine sắp chữ hoàn chỉnh. Các yêu cầu phức tạp như căn đều đoạn văn theo thuật toán Knuth, từ điển gạch nối từ, phân trang bảng lồng nhau và tuân thủ chuẩn PDF/A được xử lý tốt nhất bởi các thư viện native được biên dịch sang WebAssembly.

+-----------------------------------------------------------------------+
|                    WebAssembly PDF Engine Pipeline                    |
+-----------------------------------------------------------------------+
|   Main Application Thread                                             |
|        |                                                              |
|        | Document Spec (JSON AST: sections, tables, styles)           |
|        v                                                              |
|   Dedicated Dedicated Worker                                          |
|   +---------------------------------------------------------------+   |
|   |  Native Compiled Engine (Rust / C++ via wasm32-unknown-emscripten)|
|   |  - HarfBuzz WASM (Bidirectional complex text shaping)         |   |
|   |  - Libdeflate WASM (High-throughput FlateDecode compression)   |   |
|   |  - Pagination Engine (Widow/Orphan break optimization)        |   |
|   |                                                               |   |
|   |  Linear Memory Buffer:                                        |   |
|   |  [ Object Graph ] -> [ Subsetter ] -> [ Deflate ] -> [ XRef ] |   |
|   +---------------------------------------------------------------+   |
|        |                                                              |
|        | Transferable ArrayBuffer (Zero-Copy)                         |
|        v                                                              |
|   Blob / URL.createObjectURL (Instant in-browser preview / download)  |
+-----------------------------------------------------------------------+

Định hình văn bản bằng HarfBuzz trong WebAssembly

Khi sắp chữ cho các ngôn ngữ phức tạp (như tiếng Ả Rập, Devanagari hoặc các chữ ghép Latin nối liền như "fi" và "fl"), việc kết xuất ký tự không thể chỉ dựa vào phép thay thế ký tự sang glyph đơn giản. Các glyph sẽ thay đổi hình dạng, định vị lại theo chiều dọc và kết hợp dựa trên ngữ cảnh xung quanh.

Biên dịch engine định hình văn bản tiêu chuẩn HarfBuzz sang WebAssembly cho phép định hình văn bản hai chiều chính xác trực tiếp trong trình duyệt. HarfBuzz tiếp nhận các điểm mã Unicode và bảng phông chữ OpenType, xuất ra các chỉ số glyph chính xác cùng tọa độ bù độ phân giải dưới pixel (x,y)(x, y) cho từng ký tự.

Nén luồng thông lượng cao qua Libdeflate

Các luồng nội dung PDF và tệp nhị phân phông chữ nhúng phải được nén bằng thuật toán FlateDecode (RFC 1951 Deflate). Mặc dù trình duyệt cung cấp sẵn API CompressionStream, nhưng tính khả dụng của chúng trên các ngữ cảnh worker và khả năng tinh chỉnh tỷ lệ nén có thể không đồng đều.

Tích hợp bản dựng WebAssembly của libdeflate cung cấp khả năng nén đa luồng siêu nhanh trực tiếp bên trong worker thread, giảm thời gian tạo PDF từ vài giây xuống chỉ còn vài mili giây.

5. Công cụ web bảo vệ quyền riêng tư: Mô hình tài liệu PetexSpace

Kiến trúc của PetexSpace dành cho các công cụ PDF trên trình duyệt loại bỏ hoàn toàn các rủi ro về bảo mật, quyền riêng tư và độ trễ vốn có của các nền tảng tài liệu dựa trên đám mây truyền thống. Bạn cũng có thể chuyển đổi tài liệu phía client mà không cần truyền các tệp nguồn nhạy cảm qua mạng internet.

+-----------------------------------------------------------------------+
|                  PetexSpace Local Document Pipeline                   |
+-----------------------------------------------------------------------+
|  User Data Ingest (Client-Side Browser Sandbox)                       |
|       |                                                               |
|       v                                                               |
|  [ Web Worker: Vector Engine & HarfBuzz WASM ]                        |
|       |                                                               |
|       v                                                               |
|  [ FlateStream Compression & PDF Assembly ]                           |
|       |                                                               |
|       v                                                               |
|  [ In-Memory Binary Buffer (Uint8Array) ]                             |
|       |                                                               |
|       +---> Blob (type: 'application/pdf')                            |
|       |                                                               |
|       v                                                               |
|  [ URL.createObjectURL(blob) ] ------------------------------------+  |
|                                                                    |  |
|  +-----------------------------------------------------------------+  |
|  | Immediate Native Display via PDF.js Canvas                         |
|  | Instantaneous Direct Local File Download                           |
|  | ZERO Bytes Transmitted Outside Local Host                          |
|  +--------------------------------------------------------------------+
+-----------------------------------------------------------------------+

Ranh giới dữ liệu không truyền ra ngoài (Zero-Transmission)

Khi một tổ chức chuyển đổi bảng tính tài chính, hợp đồng pháp lý hoặc hồ sơ y tế bằng các công cụ chuyển đổi web cũ, các tệp đó sẽ được tải lên các server từ xa dùng chung tài nguyên (multi-tenant). Điều này khiến tài sản của tổ chức đối mặt với rủi ro lưu giữ dữ liệu, cấu hình sai server và vi phạm các quy định tuân thủ như GDPR và HIPAA.

PetexSpace triển khai xử lý hoàn toàn phía client trên mọi công cụ tài liệu. Quy trình tạo PDF hoạt động hoàn toàn bên trong không gian bộ nhớ cô lập của trình duyệt người dùng:

// Deterministic in-memory PDF export trigger
export function exportDocumentToLocalFile(pdfBytes: Uint8Array, filename: string): void {
  // Construct ephemeral blob pointing to in-memory byte buffer
  const blob = new Blob([pdfBytes], { type: 'application/pdf' });
  const objectUrl = URL.createObjectURL(blob);

  // Trigger synthetic anchor click for local browser download
  const link = document.createElement('a');
  link.href = objectUrl;
  link.download = filename;
  document.body.appendChild(link);
  link.click();

  // Clean up references and release memory immediately
  document.body.removeChild(link);
  setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
}

Loại bỏ độ trễ mạng

Bên cạnh lợi ích bảo mật, việc thực thi trực tiếp trên trình duyệt còn mang lại những ưu thế hiệu năng vượt trội. Việc loại bỏ các bước tải lên và tải xuống khứ hồi giúp giải quyết triệt để vấn đề nghẽn băng thông mạng. Các tài liệu hàng trăm trang có thể biên dịch, phân trang, trích xuất tập con phông chữ và kết xuất chỉ trong vài mili giây, mang lại khả năng xem trước trực tiếp tức thì ngay khi người dùng chỉnh sửa nội dung.

Bằng cách kết hợp thực thi WebAssembly, trích xuất tập con phông chữ TrueType và xuất đồ họa vector cấp thấp, các ứng dụng web hiện đại có thể cung cấp các công cụ xử lý tài liệu mạnh mẽ ngang tầm phần mềm máy tính để bàn với sự riêng tư dữ liệu tuyệt đối.

latexwebassemblycompilers

Biên dịch TeX Engine sang WebAssembly: Thực thi LaTeX trực tiếp trong trình duyệt web hiện đại

Khám phá kiến trúc biên dịch chéo TeX typesetting engine nguyên khối của Knuth sang WebAssembly, ảo hóa hệ thống tệp POSIX trên bộ nhớ RAM và hiện thực hóa quy trình biên dịch tài liệu hoàn toàn ở phía client.

PetexSpace
ocrwebassemblycomputer-vision

Nhận dạng ký tự quang học trong trình duyệt hiện đại: Phân tích bố cục bằng WebAssembly và AST ký tự

Nghiên cứu kỹ thuật chuyên sâu về thị giác máy tính trên trình duyệt: toán học nhị phân hóa hình ảnh, suy luận mạng nơ-ron bằng WebAssembly, phân đoạn bố cục vật lý và tái tạo AST tài liệu phân cấp.

PetexSpace
jupyterjson-schemaast

Giải mã định dạng Jupyter Notebook: JSON Schema và phân tích cú pháp đầu ra của ô

Phân tích chuyên sâu về JSON schema của nbformat, kiến trúc ô đa hình, hệ thống phân cấp MIME bundle, các vector làm sạch XSS và quy trình chuyển đổi phía client.

PetexSpace
web-architecturelocal-firstprivacy

Kiến trúc Web Ưu tiên Cục bộ (Local-First): Thiết kế Ứng dụng Tiện ích Cao Không Phụ thuộc Máy chủ

Hướng dẫn chiến lược kiến trúc về việc xây dựng các tiện ích web linh hoạt, bảo toàn quyền riêng tư, hoạt động tự chủ trên phần cứng người dùng, khám phá đồng bộ hóa phản ứng, khả năng ngoại tuyến và bề mặt tấn công không máy chủ.

PetexSpace