IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime9/69-dars14 daqiqa
Mundarija (23)

PDF generatsiya: PDFKit, pdfmake va brauzerdan PDF

Qisqacha: Sertifikat, chek yoki hisobotni PDF qilishning uch yo'li bor. PDFKit — har matn va chiziqni koordinata bilan o'zingiz chizasiz; pdfmake — hujjatni JSON (jadval, uslub) bilan tasvirlaysiz; headless brauzer (page.pdf()) — tayyor HTML/CSS shablonni bosmaga chiqarasiz. O'zbek tili uchun asosiy tuzoq — shrift. Standart Helvetica ʻ va kirill harflarini bilmaydi. To'liq shriftni (masalan DejaVu Sans) embed qilish kerak. pdfmake server rejimida tashqi URL va lokal fayllarni sukutda o'qiydi — siyosat bilan yoping.

Bu darsda

  • PDF ichida shrift qanday ishlashini va nega Oʻ yo'qolishi mumkinligini tushunasiz.
  • PDFKit bilan sahifalanadigan hisobotni chizasiz, sahifa raqamlarini qo'yasiz.
  • pdfmake'ning deklarativ hujjatini va xavfsizlik siyosatlarini bilasiz.
  • HTML shablonni headless Chromium bilan PDF qilasiz va foydalanuvchi matnini ekranlaysiz.
  • Uch usulni hajm, tezlik va resurs bo'yicha solishtirib, birini tanlaysiz.

Oldin bilishingiz kerak: Template engine'lar va server-side HTML, Rasmlarni qayta ishlash: sharp, SSRF va tashqi URL'lar bilan ishlash, Stream nima: to'rt turi va rejimlari.

Oldingi ikki darsda foydalanuvchi yuborgan rasm va videoni qayta ishladik. Endi teskari yo'nalish: server o'zi hujjat yasaydi.

1. Nega bu kerak?

Jasur aka oy oxirida "oktabr bo'yicha vazifalar hisoboti"ni direktorga yuborishi kerak. Bu PDF bo'lishi kerak: chop etiladi, har qurilmada bir xil ko'rinadi, o'zgartirib bo'lmaydi (oson). Boshqa misollar:

  • kurs tugatgani haqidagi sertifikat;
  • to'lov cheki (38-qism bozor loyihasida);
  • shartnoma yoki ariza — imzo uchun.

HTML sahifani "Chop etish → PDF" qilish foydalanuvchiga qoladi va har brauzerda boshqacha. Server yasagan PDF esa bir xil, avtomatik va xatga ilova qilinadi.

2. PDF ichida shrift

2.1 14 standart shrift va WinAnsi

PDF fayl matnni harflar sifatida emas, shrift gliflari (belgi rasmlari) sifatida saqlaydi. PDF standarti 14 ta "standart" shriftni (Helvetica, Times, Courier…) belgilaydi — ular faylga qo'shilmaydi, ko'ruvchi dastur o'zida bor deb hisoblanadi. Bu shriftlar WinAnsi (Windows-1252) kodlashida — 256 belgi.

Kanon PDFKit'ning standart Helvetica'si bilan o'zbekcha matn yozdi:

text
hajm 1528 bayt; Helvetica embed qilinmaydi (14 standart shrift). Kodlash WinAnsi — ʻ, ў, қ, ғ, ҳ uchun glif YO'Q → PDF da noto'g'ri belgi/bo'shliq (vizual tekshiruv: brauzer PDF ko'ruvchisida — sinalmadi, faqat kodlash bo'yicha)

Qaysi belgilar WinAnsi'ga sig'maydi — tekshiramiz:

js
// WinAnsi: ASCII, 0xA0–0xFF va 0x80–0x9F o'rnidagi belgilar
const CP1252 = "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ";
function winAnsidaYoq(matn) {
  const yoq = new Set();
  for (const ch of matn) {
    const k = ch.codePointAt(0);
    const bor = (k < 0x80) || (k >= 0xa0 && k <= 0xff) ||
      CP1252.includes(ch);
    const kod = k.toString(16).toUpperCase().padStart(4, "0");
    if (!bor) yoq.add(`${ch} U+${kod}`);
  }
  return yoq.size ? [...yoq].join(", ") : "(hammasi bor)";
}
console.log(winAnsidaYoq("Oʻzbekiston, gʻalaba"));
console.log(winAnsidaYoq("O'zbekiston, g'alaba, o’qish"));
console.log(winAnsidaYoq("ўқиш, ғоя"));

Konsolda:

text
ʻ U+02BB
(hammasi bor)
ў U+045E, қ U+049B, и U+0438, ш U+0448, ғ U+0493, о U+043E, я U+044F
  • ʻ (U+02BB) — o'zbek lotin alifbosidagi rasmiy "o'" belgisi — WinAnsi'da yo'q.
  • Oddiy apostrof ' va tipografik ’ (U+2019) — bor. Shuning uchun ba'zan "ishlaganday" ko'rinadi.
  • Kirill — umuman yo'q.

Xulosa: o'zbek matni uchun standart shriftga tayanmang.

Tekshirib ko'ring: Dasturchi sinab ko'rdi: O'zbekiston (oddiy apostrof bilan) Helvetica'da to'g'ri chiqdi. "Muammo yo'q" degan xulosa to'g'rimi?

Javob

Yo'q. Oddiy apostrof WinAnsi'da bor, shuning uchun sinov o'tdi. Lekin foydalanuvchilar ʻ (U+02BB) yoki kirill bilan yozadi — ular chiqmaydi. Sinov ma'lumoti haqiqiy ma'lumotdagi barcha belgilarni qamrashi kerak.

2.2 Shriftni embed qilish

Yechim — o'zbek harflari bor shriftni (TTF/OTF) PDF ichiga qo'shish. Kanon DejaVu Sans ni tanladi — erkin litsenziya bilan tarqatiladi (Arial — yo'q: uning litsenziyasi faylni tarqatishga ruxsat bermaydi). Avval glif bormi — tekshirildi (fontkit kutubxonasi bilan):

text
ʻ U+02BB: DejaVu bor
’ U+2019: DejaVu bor
ў U+045E: DejaVu bor
қ U+049B: DejaVu bor
ғ U+0493: DejaVu bor
ҳ U+04B3: DejaVu bor
₽ U+20BD: DejaVu bor

Embed qilish ikki narsani beradi:

  • Subset (qism) — faqat ishlatilgan gliflar qo'shiladi, butun shrift (700 KB) emas.
  • ToUnicode jadvali — PDF dan matnni nusxalash va qidirish to'g'ri ishlaydi.

Diqqat: Shrift faylini loyihaga qo'shishdan oldin litsenziyasini o'qing. DejaVu, Noto, Inter, PT Sans — embed va tarqatishga ruxsat beradi. Tizim papkasidagi shriftlar (C:\Windows\Fonts) — har doim ham emas.

3. PDFKit: qo'lda chizish

bash
npm i pdfkit@0.20.2

PDFKit hujjati — oqim (stream): doc.pipe(res) bilan to'g'ridan javobga yoki buferga. Kanon hisobotining asosiy qismi (telefon uchun bo'lingan):

js
import PDFDocument from "pdfkit";

const doc = new PDFDocument({
  size: "A4", margin: 50, bufferPages: true,
  info: { Title: "Vazifalar hisoboti", Author: "vazifalar-api" },
});
doc.registerFont("asosiy", "fonts/DejaVuSans.ttf")
  .registerFont("qalin", "fonts/DejaVuSans-Bold.ttf");
doc.font("qalin").fontSize(18).text("Vazifalar hisoboti — oktabr");
doc.moveDown().font("asosiy").fontSize(11);

let y = doc.y;
for (const [n, matn, holat] of qatorlar) {
  if (y > 760) { doc.addPage(); y = 50; }
  doc.text(n, 50, y, { width: 30 })
    .text(matn, 90, y, { width: 330 })
    .text(holat, 430, y, { width: 100 });
  y += 18;
}

const s = doc.bufferedPageRange();
for (let i = s.start; i < s.start + s.count; i++) {
  doc.switchToPage(i);
  doc.fontSize(9).text(`${i + 1} / ${s.count}`, 50, 800,
    { align: "center", width: 495, lineBreak: false });
}
doc.end();

Asosiy g'oyalar:

  • Koordinatalar — punktlarda (1 pt = 1/72 dyuym); A4 = 595 × 842 pt. y > 760 — sahifa pastiga yetdik, yangi sahifa.
  • Sahifa raqami "n / N" — N oxirida ma'lum bo'ladi. bufferPages: true sahifalarni xotirada ushlaydi, oxirida har biriga qaytib (switchToPage) raqam yoziladi.
  • lineBreak: false — raqam pastki chegaradan oshib, yangi sahifa ochib yubormasin.

Kanon natijasi (40 qator, DejaVu subset):

text
hajm 19.9 KB, sahifalar 4, FontFile2 (embed) 2 ta, ToUnicode 2 ta (matnni nusxalash/qidirish uchun)
vaqt: mediana 40.0 ms, p95 53.3 ms
  • 4 sahifa, 19.9 KB — ikki shrift (oddiy va qalin) subset bilan.
  • FontFile2 — embed qilingan TrueType shrift; ToUnicode — nusxalash uchun jadval.
  • 40 ms — 20 marta yasashning medianasi.

4. pdfmake: deklarativ hujjat

bash
npm i pdfmake@0.3.12

pdfmake'da chizmaysiz — tasvirlaysiz. Hujjat — oddiy JS obyekti (document definition):

js
import pdfmake from "pdfmake";

pdfmake.setFonts({ DejaVu: {
  normal: "fonts/DejaVuSans.ttf", bold: "fonts/DejaVuSans-Bold.ttf",
  italics: "fonts/DejaVuSans.ttf",
  bolditalics: "fonts/DejaVuSans-Bold.ttf",
} });
pdfmake.setUrlAccessPolicy(() => false);
pdfmake.setLocalAccessPolicy((yol) => yol.startsWith("fonts/"));

const buf = await pdfmake.createPdf({
  content: [
    { text: "Vazifalar hisoboti", style: "s" },
    { table: { headerRows: 1, widths: [30, "*", 90], body: [
      ["#", "Vazifa", "Holat"], ...qatorlar,
    ] } },
  ],
  defaultStyle: { font: "DejaVu", fontSize: 11 },
  styles: { s: { fontSize: 18, bold: true } },
}).getBuffer();

Jadval sahifaga sig'masa — o'zi keyingi sahifaga o'tadi, headerRows: 1 sarlavha qatorini har sahifada takrorlaydi. widths: [30, "*", 90] — "*" qolgan joyni oladi. PDFKit'da bularning hammasini qo'lda yozardingiz.

4.1 Kanon topilmasi: sukut siyosati yo'q

text
hajm 22.4 KB; sukut ogohlantirishlari:
  [warn] No URL access policy defined. Consider using setUrlAccessPolicy() to restrict external resource downloads.
  [warn] No local access policy defined. Consider using setLocalAccessPolicy() to restrict local file system access.
vaqt (policy bilan): mediana 43.2 ms
urlAccessPolicy false — tashqi rasm URL (SSRF sinovi): Access to local file denied by resource access policy: http://169.254.169.254/latest/meta-data/x.png

pdfmake 0.3 server rejimida hujjatdagi image: "http://…" yoki fayl yo'lini o'zi yuklaydi. Ogohlantirishlar: "URL kirish siyosati belgilanmagan" va "lokal kirish siyosati belgilanmagan". Agar hujjat tarkibiga foydalanuvchi ma'lumoti tushsa (masalan, "avatar URL"), hujumchi:

  • http://169.254.169.254/… — bulut metama'lumot xizmati (SSRF);
  • /etc/passwd yoki .env — server fayli (LFI).

Siyosat bilan tashqi URL rad etildi. Xabarga e'tibor bering: URL uchun ham "Access to local file denied" deyiladi — kutubxona xabari noaniq, lekin rad etildi.

Diqqat: pdfmake'ni serverda ishlatsangiz, birinchi qator — setUrlAccessPolicy(() => false) va faqat shrift papkasiga ruxsat beruvchi setLocalAccessPolicy. Rasm kerak bo'lsa — o'zingiz omborda o'qib, bufer (data URL) qilib bering.

5. Headless brauzer: HTML → PDF

5.1 page.pdf()

Eng moslashuvchan yo'l: dizayner HTML/CSS shablon yasaydi, brauzer uni chop etadi. Kanon Playwright (playwright-core 1.62.0 + Chromium 151) bilan:

js
import { chromium } from "playwright-core";

const brauzer = await chromium.launch();
const sahifa = await brauzer.newPage();
await sahifa.setContent(htmlShablon);
const pdf = await sahifa.pdf({
  format: "A4", printBackground: true,
  displayHeaderFooter: true,
  footerTemplate: `<div style="font-size:8pt;width:100%;
    text-align:center"><span class="pageNumber"></span> /
    <span class="totalPages"></span></div>`,
  margin: { top: "15mm", bottom: "15mm" },
});
await brauzer.close();

pageNumber, totalPages — Chromium o'zi to'ldiradigan maxsus klasslar. Shrift — brauzer biladigan har qanday shrift (@font-face bilan ham).

text
brauzer ishga tushishi 99 ms; birinchi PDF 237 ms; keyingilari mediana 12 ms; hajm 33.2 KB
  • Brauzerni ishga tushirish — 99 ms, birinchi PDF — 237 ms.
  • Keyingilari — 12 ms: iliq brauzerda eng tez usul.
  • Lekin brauzer — alohida jarayon, 100+ MB xotira. Kanon xulosasi: "iliq brauzerda eng tez, sovuq start bilan eng sekin".

Shuning uchun brauzer har so'rovda emas, bir marta ishga tushiriladi va qayta ishlatiladi (bir nechta page). Puppeteer — xuddi shu g'oya; kanon uni sinamadi (Playwright yetarli).

5.2 Foydalanuvchi matnini ekranlash

Shablonga vazifa matni tushadi. Hujumchi vazifani shunday nomladi:

js
const ALMASH = { "&": "&amp;", "<": "&lt;", ">": "&gt;",
  '"': "&quot;", "'": "&#39;" };
const html = (s) => String(s).replace(/[&<>"']/g, (c) => ALMASH[c]);
const matn = `<img src="http://169.254.169.254/x"` +
  ` onerror="alert(1)">`;
console.log(`<td>${html(matn)}</td>`);
console.log(html("O'qish & yozish"));

Konsolda:

text
<td>&lt;img src=&quot;http://169.254.169.254/x&quot; onerror=&quot;alert(1)&quot;&gt;</td>
O&#39;qish &amp; yozish

Ekranlashsiz headless brauzer <img> ni haqiqiy teg deb ko'rib, server ichidan 169.254.169.254 ga so'rov yuboradi (SSRF), skriptni bajaradi. PDF yasovchi brauzer — server ichidagi brauzer: u ichki tarmoqni ko'radi. Himoya: har foydalanuvchi qiymatini ekranlash (shablon dvigateli buni sukutda qiladi — Template engine'lar), qo'shimcha ravishda sahifada tarmoq so'rovlarini bloklash (Playwright page.route) — kanonda sinalmagan.

6. Qaysi birini tanlash

PDFKit pdfmake page.pdf()
Hajm (kanon, 40 qator) 19.9 KB 22.4 KB 33.2 KB
Vaqt 40 ms 43 ms 12 ms (iliq), 237 ms (birinchi)
Joylashuv qo'lda koordinata JSON, avtomatik HTML/CSS
Jadval, sahifalash qo'lda tayyor CSS (@page, break-inside)
Resurs kichik kichik brauzer jarayoni, 100+ MB
Kim tahrirlaydi dasturchi dasturchi dizayner ham
flowchart TD
  A{Hujjat qanday?} -->|chek, yorliq, sertifikat: aniq joylashuv| P[PDFKit]
  A -->|jadvalli hisobot, ko'p sahifa| M[pdfmake]
  A -->|murakkab dizayn, dizayner shabloni| C[Headless brauzer]
  C --> W[Alohida worker, bitta iliq brauzer]

Vaqtlar — bitta mashinada, bitta o'lchov. Asosiy farq tezlikda emas, qulaylik va resursda.

7. Fon rejimi

100 sahifalik hisobot yoki 500 ta sertifikat — soniyalar. So'rov ichida yasamang:

  1. Foydalanuvchi "Hisobot yasash" ni bosadi → API navbatga vazifa qo'yadi, 202 qaytaradi.
  2. Worker PDF yasaydi va omborga yozadi (hisobotlar/<egasi>/<uuid>.pdf).
  3. Foydalanuvchiga bildirishnoma yoki xat: muddatli havola bilan (Presigned URL'lar).

Kichik chek (1 sahifa, 40 ms) — so'rov ichida ham bo'ladi: doc.pipe(res) bilan oqim, faylni hech qayerga yozmasdan.

8. Hujumchi nigohi

  1. HTML injection → SSRF/XSS headless brauzerda. Himoya: ekranlash, tarmoqni cheklash.
  2. pdfmake URL/fayl o'qish. Himoya: setUrlAccessPolicy, setLocalAccessPolicy.
  3. Resurs tugashi. 10 000 qatorli hisobotni hamma bir vaqtda so'raydi. Himoya: navbat, limit, kesh (bir xil hisobot — bir marta).
  4. Begona hisobot. GET /hisobot?egasi=8. Himoya: egasi — sessiyadan (28-qism), so'rov parametridan emas.
  5. PDF ichidagi metama'lumot. Author, Creator maydonlariga ichki ma'lumot tushmasin (server nomi, foydalanuvchi emaili).

9. Ko'p uchraydigan xatolar

Xato Sabab Davosi
Oʻ o'rniga bo'shliq yoki g'alati belgi standart shrift (WinAnsi) to'liq shriftni embed
Matnni nusxalab bo'lmaydi ToUnicode yo'q embed (PDFKit/pdfmake o'zi qo'shadi)
Sahifa raqami "1 / ?" N oldindan noma'lum bufferPages + oxirida yozish
Matn sahifadan chiqib ketdi y tekshiruvi yo'q addPage yoki pdfmake
pdfmake ogohlantirishi "No URL access policy" siyosat yo'q ikkala siyosatni o'rnating
Har PDF 300 ms har safar yangi brauzer bitta brauzer, qayta ishlatish
Server xotirasi to'ldi ko'p brauzer jarayoni worker, parallellik limiti

10. Mashqlar

Mashqlar kurs/mashqlar/29/09-pdf/ da.

1-mashq (oson): bo'shliqlarni to'ldiring

  • PDF standartidagi faylga qo'shilmaydigan shriftlar soni —
  • Ularning kodlashi —
  • Faqat ishlatilgan gliflarni qo'shish —
  • PDFKit'da "n / N" uchun sahifalarni ushlab turish parametri —
  • pdfmake'da tashqi URL'larni yopish —
Yechim
  • 14 — Helvetica, Times, Courier oilalari va Symbol, ZapfDingbats.
  • WinAnsi — Windows-1252, 256 belgi; ʻ yo'q.
  • subset — PDF kichik bo'ladi.
  • bufferPages — keyin switchToPage.
  • setUrlAccessPolicy — va lokal fayllar uchun setLocalAccessPolicy.

2-mashq (o'rta): sahifalarga bo'lish

Hisobot birinchi sahifasiga sarlavha sababli 30 qator, keyingilariga 40 qator sig'adi. sahifalar(qatorlar, birinchi = 30, keyingi = 40) funksiyasini yozing: har sahifa uchun "n / N: a–b" satrini qaytarsin (qator raqamlari 1 dan). 40, 100 va 0 qator bilan sinang.

Yechim
js
function sahifalar(qatorlar, birinchi = 30, keyingi = 40) {
  const natija = [];
  let i = 0;
  while (i < qatorlar) {
    const sigim = natija.length === 0 ? birinchi : keyingi;
    const oxir = Math.min(qatorlar, i + sigim);
    natija.push(`${i + 1}–${oxir}`);
    i = oxir;
  }
  return natija.map((q, n) => `${n + 1} / ${natija.length}: ${q}`);
}
console.log(sahifalar(40));
console.log(sahifalar(100));
console.log(sahifalar(0));

Konsolda:

text
[ '1 / 2: 1–30', '2 / 2: 31–40' ]
[ '1 / 3: 1–30', '2 / 3: 31–70', '3 / 3: 71–100' ]
[]

N (jami sahifa) faqat hammasi taqsimlangandan keyin ma'lum — shuning uchun avval ro'yxat, keyin map bilan raqam. PDFKit'dagi bufferPages aynan shu g'oya. 0 qator — bo'sh hisobot: haqiqiy ilovada "ma'lumot yo'q" sahifasini chiqaring.

3-mashq (qiyin): pdfmake hujjati

hujjat(royxat) funksiyasini yozing: { matn, bajarildi } massividan pdfmake hujjat obyektini qaytarsin — sarlavha (style: "s") va jadval: headerRows: 1, widths: [30, "*", 90], birinchi qator ["#", "Vazifa", "Holat"], keyin ["1", matn, "bajarildi" | "kutilmoqda"]. Jadval tanasini JSON qilib va qatorlar sonini chiqaring.

Yechim
js
const vazifalar = [
  { matn: "Non olish", bajarildi: true },
  { matn: "Oʻqish: 1-bob", bajarildi: false },
];
function hujjat(royxat) {
  return {
    content: [
      { text: "Vazifalar hisoboti", style: "s" },
      {
        table: {
          headerRows: 1,
          widths: [30, "*", 90],
          body: [
            ["#", "Vazifa", "Holat"],
            ...royxat.map((v, i) => [String(i + 1), v.matn,
              v.bajarildi ? "bajarildi" : "kutilmoqda"]),
          ],
        },
      },
    ],
    defaultStyle: { font: "DejaVu", fontSize: 11 },
  };
}
const d = hujjat(vazifalar);
console.log(JSON.stringify(d.content[1].table.body));
console.log(d.content[1].table.body.length, "qator");

Konsolda:

text
[["#","Vazifa","Holat"],["1","Non olish","bajarildi"],["2","Oʻqish: 1-bob","kutilmoqda"]]
3 qator

Hujjat — oddiy ma'lumot. Uni test bilan tekshirish oson, PDF baytlarini solishtirish shart emas. pdfmake matnni HTML deb talqin qilmaydi: teg yozilsa, u oddiy matn bo'lib chiqadi. Lekin image maydoniga foydalanuvchi URL'ini hech qachon bermang. String(i + 1) — pdfmake jadval katakchasida satr kutadi.

11. Loyiha qadami: kanon tajribasi, loyihaga hali kirmaydi

Bu darsda vazifalar-api ga kod qo'shilmaydi. Kanon uch usulni faqat tajribada solishtirdi (glif tekshiruvi, Helvetica, PDFKit + DejaVu, pdfmake siyosatlari, Playwright). Sabab: hisobot eksporti uchun avval navbat kerak (BullMQ asoslari), va eksport formati sifatida jadvallar uchun CSV/Excel tabiiyroq — keyingi dars.

O'zingiz sinab ko'ring: alohida papkada npm i pdfkit@0.20.2, DejaVu Sans shriftini (dejavu-fonts.github.io) fonts/ ga qo'ying va «PDFKit: qo'lda chizish» bo'limidagi kodni 40 qator bilan yurgizing. Keyin registerFont ni olib tashlab, Helvetica bilan Oʻzbekiston yozib, natijani PDF ko'ruvchida solishtiring.

12. Real ishda

  • Chek, hisob-varaq, yorliq kabi qat'iy shakldagi hujjatlar ko'pincha serverda dasturiy chizish (PDFKit tipidagi) bilan yasaladi.
  • SaaS hisobotlar — HTML shablon + headless Chromium, alohida "renderer" xizmatida.
  • Elektron hujjat aylanishi — PDF/A (arxiv formati) va raqamli imzo talab qilinadi; bu kursda yo'q, hujjat bo'yicha o'rganing.
  • Intervyuda: "PDF'da o'zbek harflari nega chiqmaydi?", "HTML'dan PDF yasashda qanday xavf bor?", "1 000 ta sertifikatni qanday yasaysiz?"

Xulosa

  • 14 standart shrift WinAnsi'da: ʻ va kirill yo'q — to'liq shriftni embed qiling (subset + ToUnicode).
  • PDFKit — koordinatali chizish, eng kichik fayl; "n / N" — bufferPages.
  • pdfmake — deklarativ JSON, jadval va sahifalash tayyor; serverda URL va lokal siyosatini albatta o'rnating.
  • page.pdf() — HTML/CSS shablon; iliq brauzerda 12 ms, lekin brauzer jarayoni og'ir.
  • Foydalanuvchi matni shablonda ekranlanadi — aks holda server ichidagi brauzer orqali SSRF.
  • Katta hisobot — navbat va worker, natija omborda, havola muddatli.

Keyingi dars: Excel va CSV: eksport va import — 100 000 qatorni xotirani to'ldirmasdan eksport qilish va foydalanuvchi jadvalini xavfsiz import qilish.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!