IlmHamroh
JavaScript Full-stack/21-qism. React: media, integratsiyalar, PWA14/32-dars20 daqiqa
Mundarija (24)

PDF React'da: ko'rsatish, chek yaratish va chop etish

Qisqacha: PDF'ni ko'rsatishning eng arzon yo'li — brauzerning o'z ko'ruvchisi (<iframe> yoki havola). O'z interfeysingiz kerak bo'lsa — react-pdf (pdf.js, alohida worker'da). Chek yoki hisobotni brauzerda yaratish — jsPDF (buyruqlar: text, line) yoki @react-pdf/renderer (React komponentlari). Ko'pincha esa PDF kutubxonasi umuman kerak emas: window.print() + @media print — foydalanuvchi "PDF sifatida saqlash" ni tanlaydi. Narx katta: jsPDF +229.2 KB gzip, @react-pdf/renderer +429.9, react-pdf +550.9. Standart shriftlarda kirill harflari yo'q. Rasmiy hujjat, imzo va katta hisobot — serverda.

Bu darsda

  • PDF fayl ichida nima borligini kichik qo'lda yozilgan generator orqali ko'rasiz.
  • PDF'ni iframe va react-pdf bilan ko'rsatasiz, pdf.js nega worker'da ishlashini tushuntirasiz.
  • jsPDF va @react-pdf/renderer bilan chek yaratasiz va kirill shrift tuzog'ini bilasiz.
  • Chop etish CSS'i va window.print() bilan kutubxonasiz "PDF" qilasiz.
  • PDF qachon klientda, qachon serverda yaratilishini asoslab tanlaysiz.

Oldin bilishingiz kerak: Rasm UI React'da, File va Blob, Foydalanuvchi afzalliklari va qurilma media'lari (@media print), Web Workers, XSS va DOM xavfsizligi.

1. Nega bu kerak?

Oldingi darsda video va audio bilan ishladik. Endi hujjatlar. «Bahor» kassasida uch xil talab bor:

  • chek — mijoz buyurtmani to'lagach, "Chekni yuklab olish" tugmasi (80 mm lik termal printer o'lchamida);
  • menyu — restoran menyusi PDF fayl, saytda varaqlab ko'rish;
  • oylik hisobot — Jasur aka buxgalterga yuboradigan jadval.

PDF (Portable Document Format) — "ko'chma hujjat": har qurilmada, har printerda bir xil ko'rinadi. Sahifa o'lchami qat'iy, shriftlar fayl ichida. Veb sahifa esa ekranga moslashadi. Bu farq butun darsning asosi: PDF yaratish — har bir harfning joyini santimetrgacha aniq aytish demak.

Hayotiy o'xshatish: veb sahifa — suv, idishga qarab shakl oladi. PDF — muz: shakli muzlatilgan.

2. PDF ichida nima bor

2.1 Qo'lda yozilgan PDF — o'quv modeli

Kutubxonalar nega 200–500 KB ekanini tushunish uchun avval eng kichik PDF'ni o'zimiz yozamiz. PDF — asosan matn fayl: obyektlar (katalog, sahifalar, sahifa, mazmun, shrift) va oxirida xref jadvali — har obyekt fayl boshidan necha baytda turgani. Bu funksiya — o'quv modeli, real loyihada kutubxona ishlatiladi:

js
function makePdf(lines) {
  const text = lines
    .map((s, i) => `BT /F1 11 Tf 16 ${270 - i * 16} Td (${s}) Tj ET`)
    .join("\n");
  const objects = [
    "<< /Type /Catalog /Pages 2 0 R >>",
    "<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
    "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 226 300]" +
      " /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>",
    `<< /Length ${text.length} >>\nstream\n${text}\nendstream`,
    "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica" +
      " /Encoding /WinAnsiEncoding >>",
  ];
  let pdf = "%PDF-1.4\n";
  const offsets = [];
  objects.forEach((body, i) => {
    offsets.push(pdf.length); // obyekt qayerdan boshlanadi (bayt)
    pdf += `${i + 1} 0 obj\n${body}\nendobj\n`;
  });
  const xref = pdf.length;
  pdf += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`;
  for (const offset of offsets) {
    pdf += `${String(offset).padStart(10, "0")} 00000 n \n`;
  }
  pdf += `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\n`;
  pdf += `startxref\n${xref}\n%%EOF\n`;
  return pdf;
}

const pdf = makePdf([
  "Bahor oshxonasi",
  "Osh x1 35 000 so'm",
  "Jami: 35 000 so'm",
]);
console.log(pdf.length, "bayt");
console.log(pdf.slice(pdf.indexOf("BT"), pdf.indexOf("ET") + 2));
console.log(pdf.slice(pdf.indexOf("xref")).split("\n").slice(0, 4));

Konsolda:

text
717 bayt
BT /F1 11 Tf 16 270 Td (Bahor oshxonasi) Tj ET
[ 'xref', '0 6', '0000000000 65535 f ', '0000000009 00000 n ' ]

717 baytlik to'liq PDF. Biz uni faylga yozib, pdf.js (Firefox'ning PDF ko'ruvchisi ichidagi kutubxona) bilan o'qib tekshirdik — bitta sahifa, uch qator matn. Ikkinchi qatorni o'qing — bu sahifaga chizish buyrug'i:

Bo'lak Ma'nosi
BT … ET matn bloki boshi va oxiri
/F1 11 Tf 1-shrift, 11 punkt
16 270 Td joy: chapdan 16, pastdan 270 punkt
(…) Tj qavs ichidagi matnni chiz

Uch narsaga e'tibor bering:

  • Koordinata pastdan. PDF'da (0, 0) — sahifaning chap past burchagi. Shuning uchun har keyingi qator 270 - i * 16: pastga tushadi. Veb'dagi CSS'da esa yuqoridan.
  • O'lcham punktda. MediaBox [0 0 226 300] — 226 × 300 punkt. 1 punkt = 1/72 dyuym, 226 pt ≈ 80 mm — termal chek eni.
  • Xref — baytlar bilan. 0000000009 — birinchi obyekt 9-baytdan boshlanadi (%PDF-1.4\n — 9 bayt). Bitta harf qo'shilsa, keyingi hamma raqamlar siljiydi. Shuning uchun PDF'ni "qo'lda tahrirlash" qiyin, kutubxonalar esa bu hisobni o'zi qiladi.

2.2 Apostrof tuzog'i: /Encoding

Shrift obyektidagi /Encoding /WinAnsiEncoding ni bekorga qo'ymadik. Avval usiz sinadik va pdf.js matnni shunday o'qidi:

text
Bahor oshxonasi | Osh x1 35 000 so’m | Jami: 35 000 so’m

so'm emas, so’m — "jingalak" apostrof. Sabab: kodlash berilmasa, Helvetica PDF'ning eski "standart kodlashi"ni oladi va unda ' belgisining bayti (39) o'ng tirnoq (’) degani. WinAnsiEncoding da esa 39 — oddiy apostrof. O'zbek lotinidagi o' va g' uchun bu kichik, lekin haqiqiy farq: qidiruv va nusxa ko'chirish boshqa harf topadi.

Bundan ham katta muammo — boshqa alifbolar. Standart 14 ta PDF shriftida (Helvetica, Times, Courier …) kirill harflari yo'q. Rus tilidagi chek yoki rasmiy imlodagi ʻ belgisi (U+02BB) uchun shrift faylini PDF ichiga joylash (embed) kerak. Buni jsPDF bilan pastda ko'ramiz.

Tekshirib ko'ring: makePdf ga uchta emas, 20 ta qator bersak, nima bo'ladi?

Javob

Har qator 16 punkt pastga tushadi: 20-qator 270 - 19 × 16 = -34 — sahifaning pastki chetidan tashqarida. PDF xato bermaydi, matn shunchaki ko'rinmaydi. Haqiqiy kutubxonalar ham avtomatik sahifa qo'shmaydi (jsPDF'da addPage() ni o'zingiz chaqirasiz), @react-pdf/renderer esa matnni o'zi keyingi sahifaga o'tkazadi — bu uning ustunligi.

3. Hujumchi nigohi: PDF ichiga buyruq qo'shish

makePdf matnni qavs ichiga tekshirmasdan qo'yadi: (${s}). Taom nomini esa admin panelda xodim yozadi. Hujumchi xodim taom nomiga shuni kiritadi:

text
Osh) Tj ET BT /F1 30 Tf 16 150 Td (BEPUL

Natijada mazmun qatori (Osh) Tj ET BT /F1 30 Tf 16 150 Td (BEPUL) Tj ET bo'ladi: birinchi ) matnni yopdi, qolgani esa yangi buyruqlar — 30 punktli katta «BEPUL» yozuvi chekning o'rtasida. Biz buni sinadik: pdf.js «Osh» va alohida katta «BEPUL» ni o'qidi. Bu — PDF injection (PDF ichiga buyruq qo'shish): XSS va SQL injection bilan bir oilada — ma'lumot kodga aylandi.

Himoya — PDF satridagi maxsus belgilarni (\, (, )) teskari chiziq bilan "qochirish" (escape):

js
const pdfString = (s) => s.replace(/[\\()]/g, "\\$&");

console.log(pdfString("Osh) Tj ET BT /F1 30 Tf 16 150 Td (BEPUL"));
console.log(pdfString("Lag'mon (katta)"));

Konsolda:

text
Osh\) Tj ET BT /F1 30 Tf 16 150 Td \(BEPUL
Lag'mon \(katta\)

Endi qavslar matnning bir qismi: pdf.js butun satrni bitta matn sifatida o'qidi, «BEPUL» buyrug'i bajarilmadi. Kutubxonalar (jsPDF, @react-pdf/renderer) buni o'zi qiladi — o'z generatoringizni yozmang, yozsangiz — qochirishni unutmang.

Ikkinchi xavf — begona PDF'ni ko'rsatish. PDF ichida havolalar, formalar va hatto JavaScript bo'lishi mumkin. pdf.js'ning 4.2.67 dan eski versiyalarida (pdfjs-dist ≤ 4.1.392) maxsus yasalgan PDF ko'ruvchi sahifasida begona JavaScript bajarishga imkon beradigan zaiflik bor edi (CVE-2024-4367). Eski react-pdf versiyalari ichida aynan shunday pdf.js bor edi. Qoidalar: pdf.js/react-pdf'ni yangilab turing, foydalanuvchi yuklagan PDF'larni asosiy domeningizdan emas, alohida domendan va Content-Disposition: attachment bilan bering. Fayl turi va hajmi esa serverda qayta tekshiriladi (Fayl yuklash UI).

4. PDF'ni ko'rsatish

4.1 Brauzerning o'z ko'ruvchisi

Kompyuterdagi Chrome, Firefox, Safari va Edge'da PDF ko'ruvchi ichida qurilgan. Eng arzon yo'l:

jsx
<iframe
  src="/menyu/bahor-menyu.pdf"
  title="«Bahor» menyusi (PDF)"
  width="100%"
  height={600}
/>
<a href="/menyu/bahor-menyu.pdf" download>
  Menyuni yuklab olish (PDF, 240 KB)
</a>

0 KB JavaScript, qidiruv, kattalashtirish, chop etish — hammasi tayyor. Kamchiliklari:

  • Telefonda. Ko'p mobil brauzerlarda PDF iframe ichida ko'rinmaydi — fayl yuklab olinadi yoki alohida ilovada ochiladi. Shuning uchun yuklab olish havolasi doim yonida bo'lsin, hajmi bilan.
  • Ko'rinishni boshqarib bo'lmaydi. Tugmalar, ranglar, sahifa raqami — brauzerniki.
  • CSP. PDF iframe'da ochiladi — frame-src unga ruxsat bersin. <object>/<embed> esa object-src 'none' bilan to'siladi (vazifalar CSP'sida shunday).

4.2 react-pdf: pdf.js React'da

O'z interfeysingiz kerak bo'lsa (menyu sahifalarini kartochka qilib ko'rsatish, sahifa ustiga belgi qo'yish) — react-pdf. U Mozilla'ning pdf.js kutubxonasini React komponentlariga o'raydi. Kod hujjat bo'yicha (natija oynasida yo'q):

jsx
import { useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";
import "react-pdf/dist/Page/TextLayer.css";
import "react-pdf/dist/Page/AnnotationLayer.css";

// pdf.js faylni alohida thread'da (worker) o'qiydi
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  "pdfjs-dist/build/pdf.worker.min.mjs",
  import.meta.url,
).toString();

export function MenuPdf({ file }) {
  const [pages, setPages] = useState(0);
  return (
    <Document
      file={file}
      loading="Menyu yuklanmoqda…"
      onLoadSuccess={({ numPages }) => setPages(numPages)}
    >
      {Array.from({ length: pages }, (_, i) => (
        <Page key={i} pageNumber={i + 1} width={360} />
      ))}
    </Document>
  );
}
  • Document faylni ochadi, Page bitta sahifani canvas'ga chizadi. Ustiga shaffof matn qatlami qo'yiladi — sichqoncha bilan belgilash va nusxalash ishlaydi (shuning uchun CSS importlari).
  • Worker. PDF'ni tahlil qilish og'ir ish: 50 sahifali menyu interfeysni muzlatib qo'yardi. pdf.js uni Web Worker ga chiqaradi. workerSrc — worker skriptining manzili; Vite new URL(…, import.meta.url) ni build'da to'g'ri faylga aylantiradi.
  • Hamma sahifa birdan — kichik menyu uchun. 300 sahifali hujjatda ko'rinadigan sahifalarni chizing (virtualizatsiya g'oyasi).

Kanon tajribasida (react-pdf 11.0.0) natija halol aytilishi kerak: PDF chizilmadi. vazifalar CSP'si va Trusted Types ostida:

text
This document requires 'TrustedScriptURL' assignment. The action has been blocked.
Setting up fake worker failed: "Failed to fetch dynamically imported module: http://127.0.0.1:5870/c

Birinchi qator: worker skript manzili Trusted Types siyosatisiz to'sildi. Ikkinchisi: pdf.js "zaxira" — worker'siz ishlashga urindi, u ham ochilmadi. CSP'siz ochilganda ham ikkinchi xato takrorlandi. Demak, gap faqat CSP'da emas: worker fayli brauzer so'ragan manzilda topilmagan. Tajriba kodi yuqoridagi hujjat qatorining aynan o'zi edi. Sababini biz qazimadik va qayta o'lchamadik — shuning uchun "react-pdf ishlamaydi" demaymiz. O'z loyihangizda build'dan keyin DevTools → Network'da pdf.worker faylini toping: u 200 bilan kelishi kerak. Hajm esa aniq: +550.9 KB gzip — PDF ko'ruvchi o'zi bir ilova. Faqat PDF sahifasida, lazy bilan.

5. PDF'ni brauzerda yaratish

5.1 jsPDF: buyruqlar bilan

jsPDF — "qalam" uslubi: shu nuqtaga matn yoz, shu yerga chiziq tort, rasm qo'y. Chek (kod hujjat bo'yicha, natija oynasida yo'q):

js
import { jsPDF } from "jspdf";

const doc = new jsPDF({ unit: "mm", format: [80, 120] });
doc.setFontSize(11);
doc.text("Bahor oshxonasi", 6, 10);
doc.text("Osh x1 35 000 so'm", 6, 18);
doc.line(6, 21, 74, 21); // ajratuvchi chiziq
doc.save("chek.pdf"); // Blob → <a download>, W-55 dagi kabi
  • unit: "mm", format: [80, 120] — chek eni 80 mm. jsPDF'da koordinata yuqoridan (PDF'nikidan farqli) — kutubxona o'zi aylantiradi.
  • save — ichida o'sha retsept: Blob, obyekt URL, <a download>. Faylni serverga yuborish kerak bo'lsa — doc.output("blob").

Biz jsPDF 4.2.1 ni dars tayyorlashda Node 24 da ishga tushirdik. Kanon tajribasidagi bir qatorli chek ("Chek: osh 35 000") — 3168 bayt, xuddi brauzerdagidek. Keyin yuqoridagi chekka bitta ruscha qator qo'shdik: doc.text("Плов x1", 6, 26). pdf.js shuni o'qidi:

text
Bahor oshxonasi | Osh x1 35 000 so'm | ; > 2 |   | x 1

Apostrof joyida (jsPDF WinAnsiEncoding ni o'zi qo'yadi), lekin Плов o'rnida — ; > 2. Xato ham, ogohlantirish ham chiqmadi. Kirill uchun shrift faylini (.ttf) PDF'ga joylash kerak:

js
doc.addFileToVFS("NotoSans.ttf", notoSansBase64); // shrift — base64
doc.addFont("NotoSans.ttf", "NotoSans", "normal");
doc.setFont("NotoSans");
doc.text("Плов x1", 6, 26); // endi to'g'ri

Narxi — shrift faylining o'zi: u ham JavaScript to'plamiga (yoki alohida yuklanadigan faylga) qo'shiladi. Faqat kerakli harflarni qoldirib kichraytirish (subsetting) — yaxshi amaliyot.

html2canvas + jsPDF — mashhur, lekin xavfli yorliq: sahifadagi HTML'ni rasmga aylantirib, PDF'ga rasm qilib qo'yish. Natija tez, lekin matn rasm bo'lib qoladi — qidirib, nusxalab bo'lmaydi, ekran o'quvchi o'qimaydi, hajmi katta va xira. Biz bu juftlikni o'lchamadik. Chek uchun jsPDF'ning text(), sahifaning o'zi uchun — chop etish CSS'i.

5.2 @react-pdf/renderer: React komponentlari bilan

@react-pdf/renderer — PDF'ni React komponentlaridan yig'adi. Flexbox bilan joylashtirish, uzun matn keyingi sahifaga o'zi o'tadi:

jsx
import {
  Document,
  Page,
  Text,
  View,
  StyleSheet,
  pdf,
} from "@react-pdf/renderer";

const s = StyleSheet.create({
  page: { padding: 16, fontSize: 11 },
  row: { flexDirection: "row", justifyContent: "space-between" },
});

function Receipt({ items }) {
  return (
    <Document>
      <Page size={[226, 300]} style={s.page}>
        <Text>Bahor oshxonasi</Text>
        {items.map((item) => (
          <View key={item.name} style={s.row}>
            <Text>{item.name}</Text>
            <Text>{item.price} so'm</Text>
          </View>
        ))}
      </Page>
    </Document>
  );
}

const blob = await pdf(<Receipt items={items} />).toBlob();

Bu komponentlar sahifaga chizilmaydi — ular faqat PDF'ga aylanadi. <div> emas <View>, <p> emas <Text>; CSS'ning bir qismi ishlaydi. Narx o'ng tomonga flexbox bilan tekislandi — jsPDF'da buning uchun matn enini o'zingiz hisoblaysiz.

Kanon tajribasida (4.9.0) +429.9 KB gzip va vazifalar CSP'si ostida PDF yaratilmadi:

text
Connecting to 'data:application/octet-stream;base64,AGFzbQEAAAABugM3YAF/AGACf38AYAF/AX9gA39/fwBgAn98
Aborted(CompileError: WebAssembly.instantiate(): Compiling or instantiating WebAssembly module viola

Kutubxona joylashtirish hisobini (flexbox) WebAssembly modulida qiladi va uni data: manzildan yuklaydi. CSP ikki joyda to'sdi: connect-src (data: ga so'rov) va WebAssembly'ni kompilyatsiya qilish (script-src da 'wasm-unsafe-eval' yo'q). CSP'siz ochilganda esa bir qatorli chek yaratildi — 1528 bayt. Bu kutubxonani tanlasangiz, CSP'ni ongli kengaytirasiz.

Tekshirib ko'ring: Nega jsPDF'dagi 3168 baytlik chek bizning 717 baytlik makePdf dan 4 baravar katta, garchi matn bir xil bo'lsa ham?

Javob

jsPDF to'liqroq hujjat yozadi: metama'lumot (yaratuvchi dastur, sana), barcha standart shriftlar ro'yxati va kodlashlari, ko'proq sahifa sozlamalari. Bu "ortiqcha" baytlar turli PDF o'quvchilarda hujjat bir xil ochilishini kafolatlaydi. 3 KB baribir juda kichik — muhimi PDF fayl emas, kutubxonaning JavaScript'i: +229.2 KB.

6. Chop etish: kutubxonasiz PDF

Ko'p hollarda eng yaxshi "PDF generator" — brauzerning chop etish oynasi. Unda "PDF sifatida saqlash" bor. Siz faqat sahifaning qog'ozdagi ko'rinishini @media print bilan tayyorlaysiz:

css
@media print {
  nav,
  .print-hidden {
    display: none;
  }
  body {
    font: 11pt/1.4 system-ui, sans-serif;
    color: black;
  }
  @page {
    size: A4;
    margin: 15mm;
  }
}

React'da tugma — bitta qator:

jsx
<button className="print-hidden" onClick={() => window.print()}>
  Chop etish yoki PDF sifatida saqlash
</button>

Saytdagi natija oynasida bu tugma ishlamaydi: oyna himoyalangan (sandbox) va unda modal oynalarga — chop etish oynasi ham shunga kiradi — ruxsat yo'q. O'z loyihangizda ishlaydi.

Chop etishdan oldin sahifani tayyorlash kerak bo'lsa (masalan, yopiq details teglarini ochish), beforeprint va afterprint hodisalari bor — React'da ularga effektda obuna bo'linadi (useEffect I). Narx: 0 KB, matn tanlanadi, shriftlar brauzerniki — kirill ham muammosiz.

7. Qayerda yaratish kerak: brauzer yoki server

flowchart TB
  A{"Hujjat rasmiymi? (hisob-faktura, shartnoma, imzo)"} -- ha --> S["Server (backend)"]
  A -- yo'q --> B{"Sahifaning o'zi qog'ozga kerakmi?"}
  B -- ha --> P["window.print + @media print (0 KB)"]
  B -- yo'q --> C{"Ma'lumot brauzerda, hujjat kichikmi?"}
  C -- ha --> K["Klient: jsPDF (+229 KB, kech yuklash)"]
  C -- yo'q --> S

Diagrammaga qarang. Server tomonga uchta kuchli sabab bor:

  • Bir xil natija. Brauzerlar shriftni turlicha chizadi. Hisob-faktura har kimda bir xil ko'rinishi kerak — server bitta shrift va bitta dastur bilan yaratadi.
  • Ishonch. Klientda yaratilgan PDF'ni foydalanuvchi o'zgartira oladi: summani tahrirlab, "chek" ko'rsatadi. Rasmiy hujjat serverdagi ma'lumotdan, kerak bo'lsa imzo bilan yaratiladi.
  • Hajm. 300 sahifali yillik hisobot telefonda muzlatadi; serverda — fon ishi, tayyor bo'lgach havola.

Serverda PDF yasash (PDFKit, pdfmake yoki headless brauzerda page.pdf()) — backend qismida, PDF generatsiya darsida. Klientda esa — oflayn ishlaydigan va darhol kerak bo'ladigan narsalar: kassadagi chek, foydalanuvchining o'z ma'lumotlari.

8. Biz sinab ko'rdik: hajm va CSP

PDF kutubxonalari: JS (gzip, React'dan tashqari)
  • window.print + @media print0 KB
  • jsPDF 4.2.1CSP 0, chek 3168 bayt229,2 KB
  • @react-pdf/renderer 4.9.0CSP: WASM data: to'sildi429,9 KB
  • react-pdf 11.0.0TT: worker to'sildi550,9 KB

Manba: O'lchandi: kanon tajribasi, Vite 8.3.3, gzip -9, Chrome 155, natijalar/tajriba/kutubxonalar-21.txt, 2026-10-17

Taqqoslash uchun: vazifalar ning butun birinchi yuklashi — ~221 KB. jsPDF bittasi shunga teng, PDF ko'ruvchi — 2.5 baravar. Uchala kutubxona ham faqat kech yuklanishi kerak: foydalanuvchi «Chekni yuklab olish» ni bosgandagina (lazy va code splitting — import("jspdf") tugma hodisasida).

CSP natijasi (kutubxonalar-21.txt, xom). Qatorlar uzun — har birida faqat CSP so'zidan keyingi qismga qarang: 0 — buzilish yo'q, boshqasi — qaysi qoida to'sgani:

text
pdf-jspdf              JS  302388 (+ 229.2 KB) CSS      0 | CSP 0 natija=3168 | xato: yo'q | ochiq: tashqi yo'q
pdf-react-pdf          JS  631791 (+ 550.9 KB) CSS      0 | CSP require-trusted-types-for×1 | xato: This document requires 'TrustedScriptURL' assignment. The action has been blocked. ; Setting up fake worker failed: "Failed to fetch dynamically imported module: http://127.0.0.1:5870/c | ochiq: tashqi yo'q xato: Setting up fake worker failed: "Failed to fetch dynamically imported module: http://127.0.0.1:5870/o
pdf-renderer           JS  507953 (+ 429.9 KB) CSS      0 | CSP connect-src×1,script-src×1 | xato: Connecting to 'data:application/octet-stream;base64,AGFzbQEAAAABugM3YAF/AGACf38AYAF/AX9gA39/fwBgAn98 ; Fetch API cannot load data:application/octet-stream;base64,AGFzbQEAAAABugM3YAF/AGACf38AYAF/AX9gA39/f | ochiq: tashqi yo'q

Uchtasidan faqat jsPDF qattiq siyosat ostida toza ishladi. Hech biri tashqi serverga bormadi.

Nega vazifalar da PDF yo'q. Ro'yxatni ilovadan olib chiqish uchun JSON eksport bor, keyingi darsda CSV qo'shiladi — Excel'da ochiladi. PDF uchun kutubxona +229 KB bo'lardi, foydasi esa kam: ro'yxatni qog'ozga chiqarish kerak bo'lsa, brauzerning chop etish oynasi yetadi. Kanon PDF kutubxonasini qo'shmadi.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
ruscha matn o'rnida ; > 2 standart shriftda kirill yo'q .ttf shriftni joylash (addFont)
so'm → so’m shriftda kodlash yo'q /Encoding /WinAnsiEncoding (kutubxona o'zi qiladi)
matn sahifa tagidan chiqib ketdi PDF koordinatasi pastdan, sahifa avtomatik qo'shilmaydi addPage(); yoki @react-pdf/renderer
react-pdf: Setting up fake worker failed worker fayli o'sha manzilda yo'q Network'da worker so'rovini tekshiring; workerSrc — hujjatdagidek
requires 'TrustedScriptURL' worker manzili Trusted Types'siz siyosat yoki boshqa yo'l
WebAssembly … violates @react-pdf/renderer WASM'i 'wasm-unsafe-eval' va connect-src data: — ongli
telefonda PDF iframe bo'sh mobil brauzer inline ko'rsatmaydi yonida yuklab olish havolasi
chekda «BEPUL» paydo bo'ldi PDF satri qochirilmagan kutubxona yoki pdfString

10. Mashqlar

Mashqlar kurs/mashqlar/21/14-pdf/ da. Yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): bo'sh joylarni to'ldiring

  • PDF'da koordinata (0, 0) sahifaning chap burchagi
  • 1 punkt = 1/ dyuym
  • jsPDF'da kirill uchun shrift qo'shadigan metod: doc.(…)
  • react-pdf faylni alohida thread'da o'qiydi — bu
  • Kutubxonasiz PDF: window.() va @media print
Yechim
  • past — veb'dan farqli; har keyingi qator kichikroq y bilan.
  • 72 — 80 mm ≈ 226 punkt.
  • addFont — addFileToVFS dan keyin; so'ng setFont.
  • worker — pdf.js tahlili interfeysni muzlatmasin.
  • print — foydalanuvchi chop etish oynasida "PDF sifatida saqlash" ni tanlaydi.

2-mashq (o'rta): xavfsiz qatorlar

makePdf ni xavfsiz qiling: har qator chizishdan oldin pdfString dan o'tsin. Bundan tashqari, satrda chop etib bo'lmaydigan belgilar bo'lsa (kod nuqtasi 127 dan katta — masalan kirill yoki emoji), ular ? ga almashsin — standart shrift ularni baribir chiza olmaydi. Faqat mazmun qatorini yasaydigan contentStream(lines) funksiyasini yozing va sinang.

Ishora: belgilarni almashtirish — muntazam ifoda /[^\x20-\x7e]/g ("bo'shliqdan ~ gacha bo'lmagan hamma narsa", Belgilar va belgi klasslari).

Yechim
js
const pdfString = (s) =>
  s.replace(/[^\x20-\x7e]/g, "?").replace(/[\\()]/g, "\\$&");

function contentStream(lines) {
  return lines
    .map((s, i) => {
      const y = 270 - i * 16;
      return `BT /F1 11 Tf 16 ${y} Td (${pdfString(s)}) Tj ET`;
    })
    .join("\n");
}

console.log(contentStream(["Lag'mon (katta)", "Плов", "a\\b"]));

Konsolda:

text
BT /F1 11 Tf 16 270 Td (Lag'mon \(katta\)) Tj ET
BT /F1 11 Tf 16 254 Td (????) Tj ET
BT /F1 11 Tf 16 238 Td (a\\b) Tj ET

Tartib muhim: avval keraksiz belgilarni ? ga, keyin qochirish. Teskari tartibda ham bu yerda farq chiqmaydi, lekin "avval tozalash, keyin qochirish" — umumiy qoida: qochirilgan natijani boshqa almashtirish buzmasin. "Плов" — 4 ta ?: ma'lumot yo'qolganini foydalanuvchi ko'radi. Jim buzilgan ; > 2 dan yaxshiroq, lekin to'g'ri yechim — shrift joylash.

3-mashq (qiyin): 32 belgilik chek qatorlari

Termal printer cheki odatda 32 belgilik qatorlar va bir xil enli shrift (Courier). receiptLines(items) funksiyasini yozing:

  1. Har taom: chapda Nomi xN, o'ngda summa (70 000), oraliq bo'shliq bilan — qator aniq 32 belgi.
  2. Nom sig'masa — qisqartirilib, oxiriga .. qo'yilsin (xN va summa doim ko'rinsin).
  3. Oxirida 32 ta - va Jami: qatori.

Sinov: Osh ×2 (35 000), Ko'k choy ×1 (5 000), «Lag'mon maxsus katta porsiya» ×1 (28 000). Har qatorni |…| ichida chiqaring — eni ko'rinsin.

Ishora: son guruhlari — Grafiklar I darsidagi som() muntazam ifodasi; to'ldirish — " ".repeat(n); jami — reduce.

Yechim
js
const WIDTH = 32;

function som(n) {
  return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, " ");
}

function row(name, suffix, price) {
  const right = som(price);
  const max = WIDTH - right.length - 1 - suffix.length;
  const short =
    name.length > max ? name.slice(0, max - 2) + ".." : name;
  const left = short + suffix;
  const gap = WIDTH - left.length - right.length;
  return left + " ".repeat(gap) + right;
}

function receiptLines(items) {
  const total = items.reduce((s, i) => s + i.qty * i.price, 0);
  return [
    ...items.map((i) => row(i.name, ` x${i.qty}`, i.qty * i.price)),
    "-".repeat(WIDTH),
    row("Jami:", "", total),
  ];
}

const lines = receiptLines([
  { name: "Osh", qty: 2, price: 35000 },
  { name: "Ko'k choy", qty: 1, price: 5000 },
  { name: "Lag'mon maxsus katta porsiya", qty: 1, price: 28000 },
]);
for (const line of lines) console.log(`|${line}|`);

Konsolda:

text
|Osh x2                    70 000|
|Ko'k choy x1               5 000|
|Lag'mon maxsus katta.. x1 28 000|
|--------------------------------|
|Jami:                    103 000|

max — nom uchun qolgan joy: 32 dan summa, bitta majburiy bo'shliq va x1 ayiriladi. Bu qatorlarni makePdf ga /BaseFont /Courier bilan bersangiz, ustunlar qog'ozda ham tekis turadi — Courier'da har harf bir xil enli. Helvetica'da esa i va m turlicha keng, ustunlar siljiydi.

4-mashq: tajriba qadami (ixtiyoriy)

vazifalar ga tegmang. Alohida Vite + React loyihada «Chekni yuklab olish» tugmasini yasang: jsPDF tugma bosilganda import("jspdf") bilan kech yuklansin, chek 3-mashqdagi qatorlar bilan, setFont("courier"). Keyin ruscha qator qo'shing va shrift joylab tuzating.

Yechim

Tekshiruv ro'yxati:

  • npm i -E jspdf@4.2.1; npm run build — jsPDF alohida chunk'da, birinchi yuklashga kirmagan (kanonda ~229 KB gzip).
  • DevTools → Network: chunk faqat birinchi bosishda keladi, ikkinchisida — keshdan.
  • Yuklangan PDF'da matnni sichqoncha bilan belgilab nusxalang — so'm oddiy apostrof bilan.
  • Ruscha qator: shriftsiz — buzilgan harflar; addFileToVFS + addFont + setFont dan keyin — to'g'ri. Build hajmi shrift fayli qadar o'sadi.
  • vazifalar CSP'si bilan sinasangiz: jsPDF — buzilish yo'q (kanonda CSP 0).

11. Real ishda

  • Chek va kvitansiyalar — kassa va onlayn do'konlarda; ko'pincha server yaratadi, oflayn kassada — klient (jsPDF).
  • Hujjatlar — shartnoma, sertifikat, hisob-faktura: serverda, bir xil shrift bilan, ba'zan elektron imzo bilan.
  • Ko'rish — kitob, menyu, qo'llanma: oddiy holda brauzer ko'ruvchisi, maxsus interfeysda react-pdf.
  • Intervyu savollari: "PDF'ni klientda yoki serverda yaratish — qachon qaysi?", "html2canvas nega yomon yechim?", "pdf.js nega worker ishlatadi?", "kirill matn PDF'da nega buziladi?".

Xulosa

  • PDF — obyektlar va bayt manzillari (xref); koordinata pastdan, o'lcham punktda; satrlar qochiriladi.
  • Ko'rsatish: brauzer ko'ruvchisi (0 KB, telefonda yuklab olish havolasi bilan) yoki react-pdf (pdf.js, worker, +550.9 KB).
  • Yaratish: jsPDF (+229.2 KB, CSP 0) — buyruqlar; @react-pdf/renderer (+429.9 KB) — komponentlar, CSP'da WASM.
  • Standart shriftlarda kirill yo'q — .ttf joylash; kodlash WinAnsiEncoding (apostrof).
  • Ko'pincha eng yaxshisi — window.print() + @media print.
  • Rasmiy, katta va bir xil bo'lishi shart hujjat — serverda (PDF generatsiya).

Keyingi dars: Excel va CSV: import va eksport — vazifalar ro'yxatini Excel'da ochiladigan CSV qilib yuklab olish va Excel'dan saqlangan faylni qayta o'qish (o'z CSV o'quvchisi, PapaParse, SheetJS, ExcelJS).

Manbalar

  • PDF 1.7 (ISO 32000-1): fayl tuzilishi, matn operatorlari (BT, Tf, Td, Tj), standart shriftlar va kodlashlar
  • react-pdf — github.com/wojtekmaj/react-pdf ("Configure PDF.js worker"); pdf.js — mozilla.github.io/pdf.js
  • jsPDF — artskydj.github.io/jsPDF/docs ("addFont", "output"); @react-pdf/renderer — react-pdf.org
  • CVE-2024-4367 (pdf.js, 4.2.67 da tuzatilgan) — github.com/mozilla/pdf.js/security
  • MDN: window.print(), beforeprint/afterprint, @page
  • Kanon: natijalar/tajriba/kutubxonalar-21.txt, tajriba/kutubxonalar/src/k/pdf-*.tsx (2026-10-17); jsPDF 4.2.1 va pdf.js bilan o'qish — dars tayyorlashda, Node 24 (2026-10-18)
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
PDF React'da: ko'rsatish, chek yaratish va chop etish — IlmHamroh