ข้ามไปยังเนื้อหา

ลดขนาดไฟล์ PDF ด้วยการบีบอัดและการทำซับเซต

คุณต้องการไฟล์ PDF ที่มีขนาดเล็กที่สุดเท่าที่เนื้อหาจะเอื้ออำนวย โดยไม่สูญเสียความเที่ยงตรง NextPDF มีตัวควบคุมขนาดไฟล์สองส่วน และทั้งสองส่วนเปิดใช้งานตามค่าเริ่มต้น:

  • การบีบอัดสตรีม ตัวเขียนจะห่อสตรีมเนื้อหาของทุกหน้าและโปรแกรมฟอนต์ที่ฝังทุกตัวไว้ในสตรีม FlateDecode (zlib) ตัว NextPDF\Core\Config มีแฟล็ก compress สำหรับเก็บการตั้งค่านี้ อ่านค่ากลับได้ด้วยตัว wither withCompress() เมื่อคุณสร้างเอกสารแบบสตรีมมิง
  • การทำซับเซตฟอนต์ เมื่อคุณฝังฟอนต์ TrueType หรือ CFF ตัวเขียนจะสร้างโปรแกรมฟอนต์ขึ้นใหม่ให้มีเฉพาะกลิฟที่เอกสารใช้ จากนั้นบีบอัดผลลัพธ์ด้วย FlateDecode กระบวนการนี้เกิดขึ้นโดยอัตโนมัติ ไม่มีแฟล็กที่ต้องตั้งค่าและไม่มีการเรียกใช้ที่ต้องเพิ่ม ฟอนต์ CJK ขนาด 20,000 กลิฟ ซึ่งนำกลิฟมาใช้ในเอกสารเพียงไม่กี่ร้อยตัว จะถูกฝังด้วยขนาดเพียงเศษเสี้ยวของขนาดบนดิสก์

มีข้อจำกัดสำคัญที่ควรรู้ก่อน: NextPDF Core ไม่เปิดให้ใช้การสุ่มตัวอย่างภาพใหม่ ปุ่มปรับคุณภาพภาพ สวิตช์เปิดปิดออบเจกต์สตรีม หรือการตั้งค่าการลดข้อมูลทรัพยากรที่ซ้ำกัน ตัวควบคุมสองอย่างข้างต้นเป็นตัวควบคุมขนาดเพียงอย่างเดียว ส่วนที่เหลือของเรซิพีนี้จะแสดงวิธีใช้งานทั้งสองอย่างให้ถูกต้อง และอธิบายว่าสิ่งเหล่านี้ไม่ครอบคลุมอะไรบ้าง

ข้อกำหนดเบื้องต้น: ต้องมีการติดตั้ง Core (composer require nextpdf/core:^3) และสำหรับเส้นทางการทำซับเซต ต้องมีไฟล์ฟอนต์ที่คุณได้รับอนุญาตให้ฝัง

Terminal window
composer require nextpdf/core:^3

PDF คือโครงสร้างต้นไม้ของออบเจกต์ ออบเจกต์ที่ใหญ่ที่สุดมักเป็นสตรีมเนื้อหา (ตัวดำเนินการวาดสำหรับแต่ละหน้า) และโปรแกรมฟอนต์ (เส้นโครงร่างกลิฟที่ฝัง) ทั้งสองอย่างบีบอัดได้ดี ดังนั้นตัวควบคุมขนาดที่ได้ผลที่สุดคือการบีบอัดด้วย FlateDecode FlateDecode เป็นชื่อในมาตรฐาน PDF 2.0 สำหรับสตรีม DEFLATE ที่ห่อด้วย zlib (ISO 32000-2:2020 §7.4.4) และเป็นฟิลเตอร์ที่ NextPDF สร้างในเอาต์พุต

ตัวเขียนตรึงระดับการบีบอัด DEFLATE ไว้ที่ 9 ซึ่งเป็นค่าสูงสุดตาม RFC 1951 ผ่าน NextPDF\Writer\PinnedZlibCompressor ระดับ 9 ใช้ CPU เพิ่มขึ้นเล็กน้อยเพื่อแลกกับสตรีมที่เล็กที่สุด การตรึงระดับไว้ยังทำให้เอาต์พุตคงที่ เพราะส่วนหัว zlib เข้ารหัสระดับนั้นไว้ และระดับที่เปลี่ยนไปจะทำให้ไบต์เปลี่ยน คุณไม่ได้เลือกระดับเอง เอนจินตรึงค่านี้ไว้เพื่อให้การรันสองครั้งบนอินพุตเดียวกันสร้างสตรีมที่มีไบต์เหมือนกันทุกประการ

กลไกที่สองคือการทำซับเซตฟอนต์ ไฟล์ฟอนต์บนดิสก์มีกลิฟทุกตัวที่แบบอักษรกำหนดไว้ แต่เอกสารที่แสดงข้อความ “Invoice 2026” ต้องใช้เพียงไม่กี่ตัวเท่านั้น NextPDF\Typography\FontSubsetter (สำหรับ TrueType) และ NextPDF\Typography\CffSubsetter (สำหรับ CFF / OpenType) จะไล่ตรวจโค้ดพอยต์ที่เอกสารเรนเดอร์จริง แก้ไขการพึ่งพาของกลิฟแบบประกอบ และสร้างเฉพาะตารางฟอนต์ที่จำเป็นขึ้นใหม่ ทั้งสองตัวสร้างไบนารีฟอนต์ซับเซตที่ถูกต้องพร้อมแท็กคำนำหน้าซับเซตแบบหกตัวอักษรที่กำหนดได้แน่นอน (ISO 32000-2:2020 §9.9) ตัวเขียนจะใช้สิ่งนี้เมื่อใดก็ตามที่ทราบชุดกลิฟที่ใช้งานของฟอนต์ที่ฝัง จากนั้นบีบอัดซับเซตด้วย FlateDecode ถ้าการทำซับเซตของแบบอักษรหนึ่ง ๆ จะช่วยประหยัดได้น้อยกว่าสิบเปอร์เซ็นต์ ตัวทำซับเซตจะคืนโปรแกรมต้นฉบับแทน เพราะต้นทุนการสร้างใหม่ไม่คุ้มกับผลที่ได้เพียงเล็กน้อย

ข้อสรุปสำคัญ: คุณลดขนาดไฟล์ PDF ได้ด้วยการเปิดการบีบอัดไว้ (ค่าเริ่มต้น) และฝังไฟล์ฟอนต์จริง (เพื่อให้การทำซับเซตมีสิ่งที่จะลดขนาดได้) ไม่ใช่ด้วยการไล่ปรับตัวเลือกจำนวนมาก

ตัวควบคุมขนาดเพียงอย่างเดียวที่คุณตั้งค่าได้อยู่บนออบเจกต์การกำหนดค่า

NextPDF\Core\Config เป็น value object ที่ไม่เปลี่ยนแปลงและเป็น final readonly พร้อมเมท็อด wither ที่กำหนดชนิดไว้ สมาชิกที่เกี่ยวข้องกับขนาดคือ:

  • compress (bool ค่าเริ่มต้น true) เปิดใช้งานการบีบอัด FlateDecode อ่านค่ากลับด้วย withCompress(bool $compress): self ซึ่งคืน Config ตัวใหม่ที่เปลี่ยนแฟล็กแล้วและคงฟิลด์อื่นทุกตัวไว้

แนบ Config เข้ากับเอกสารเมื่อตอนสร้างอินสแตนซ์:

  • NextPDF\Core\Document::createStandalone(?Config $config = null): self สร้างเอกสารด้วยรีจิสทรีชั่วคราวสำหรับสคริปต์ CLI หรือโพรเซสอายุสั้น โดยใช้ Config ของคุณ

สมาชิกสองตัวกำหนดสิ่งที่ตัวควบคุมขนาดใช้ทำงาน แต่ตัวมันเองไม่ใช่ตัวควบคุมการบีบอัด:

  • imageCacheBytes (int ค่าเริ่มต้น 52_428_800) จำกัดขนาดแคชภาพในหน่วยความจำ และ withImageCacheBytes(int $bytes): self เปลี่ยนค่านี้ ค่านี้จำกัดขอบเขตหน่วยความจำสูงสุดระหว่างการสร้าง แต่ไม่ได้สุ่มตัวอย่างใหม่ บีบอัดซ้ำ หรือลดขนาดภาพที่คุณฝังด้วยวิธีอื่นใด เป็นเพดานหน่วยความจำ ไม่ใช่ตัวควบคุมขนาดเอาต์พุต
  • fontsDirectory (string) และ withFontsDirectory(string $dir): self กำหนดเส้นทางค้นหาเริ่มต้นสำหรับไฟล์ฟอนต์ ซึ่งป้อนให้เส้นทางการทำซับเซต

งานเกี่ยวกับฟอนต์เกิดขึ้นผ่านพื้นผิวการพิมพ์ของเอกสาร:

  • setFont(string $family, string $style = '', float $size = 12.0): static เลือกแบบอักษร เมื่อตระกูลฟอนต์ถูกจับคู่กับไฟล์ฟอนต์ที่ฝังได้ ตัวเขียนจะบันทึกโค้ดพอยต์ที่คุณเรนเดอร์ เพื่อให้สามารถทำซับเซตแบบอักษรนั้นได้ตอนบันทึก
  • addFontDirectory(string $directory): static ลงทะเบียนไดเรกทอรีเพิ่มเติมเพื่อค้นหาไฟล์ฟอนต์

เอาต์พุตใช้เมท็อดมาตรฐานสามรายการ: getPdfData(): string คืนค่าไบต์ save(string $path): void เขียนไบต์เหล่านั้นแบบอะตอมมิก และ output(?string $filename, OutputDestination $dest): string จัดการการส่งผ่านทาง HTTP

การทำซับเซตไม่มีเมท็อดสาธารณะและไม่มีแฟล็ก เป็นคุณสมบัติที่เกิดขึ้นเองจากการฝังฟอนต์และการเรนเดอร์ข้อความ ตัวเขียนจะขับเคลื่อน FontSubsetter / CffSubsetter ภายใน NextPDF\Writer\PdfFontWriter ให้คุณ

ตัวอย่างนี้สร้างเอกสารโดยเปิดการบีบอัดอย่างชัดเจน ใช้ฟอนต์ที่ฝังและทำซับเซตได้ จากนั้นเขียนไบต์ออกมา ตัวอย่างนี้ละเว้นการจัดการข้อผิดพลาดเพื่อให้รูปแบบการเรียกใช้ชัดเจน ตัวอย่างสำหรับการใช้งานจริงด้านล่างเพิ่มการป้องกันครบถ้วน

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;
use NextPDF\Core\Document;
// compress defaults to true; setting it explicitly documents intent.
$config = (new Config())->withCompress(true);
$doc = Document::createStandalone($config);
$doc->addFontDirectory(__DIR__ . '/fonts');
$doc->addPage();
// Selecting an embeddable face records the glyphs used, so the writer
// subsets this font automatically when the document is built.
$doc->setFont('LiberationSans', '', 12);
$doc->cell(0, 10, 'Invoice 2026 - subsetted, compressed output.', newLine: true);
$pdf = $doc->getPdfData();
file_put_contents(__DIR__ . '/small.pdf', $pdf);
printf("Wrote %d bytes.\n", strlen($pdf));

นี่คือโปรแกรมแบบสมบูรณ์ในตัวเอง โปรแกรมสร้างเอกสารโดยเปิดการบีบอัด ฝังฟอนต์จากไดเรกทอรีที่คุณควบคุม เรนเดอร์ข้อความเพื่อให้ตัวทำซับเซตมีชุดกลิฟที่ใช้งาน และเขียนผลลัพธ์แบบอะตอมมิก โปรแกรมดักจับข้อยกเว้นของ NextPDF ที่เจาะจงที่สุดซึ่งเส้นทางการสร้างและการบันทึกยกขึ้น จากนั้นโยนข้อยกเว้นนั้นซ้ำพร้อมบริบทแทนการกลืนข้อผิดพลาด กำหนด NEXTPDF_FONT_DIR ให้ชี้ไปยังไดเรกทอรีที่เก็บแบบอักษร TrueType หรือ CFF ที่คุณได้รับอนุญาตให้ฝัง โปรแกรมจะตรวจสอบความถูกต้องของเส้นทางก่อนฝัง

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;
use NextPDF\Core\Document;
use NextPDF\Exception\CompressionException;
use NextPDF\Exception\InvalidConfigException;
/**
* Resolve and validate the font directory from a server-controlled source.
*
* Reading the directory from the environment keeps the path off the request
* surface. The function rejects a missing or unreadable directory so the
* embedding path never runs against untrusted or absent input.
*/
function resolveFontDirectory(): string
{
$configured = getenv('NEXTPDF_FONT_DIR');
$dir = $configured !== false && $configured !== '' ? $configured : __DIR__ . '/fonts';
$real = realpath($dir);
if ($real === false || !is_dir($real) || !is_readable($real)) {
throw new RuntimeException(sprintf('Font directory "%s" is not a readable directory.', $dir));
}
return $real;
}
/**
* Build a compressed, font-subsetted document and return its bytes.
*
* @param non-empty-string $fontDirectory Validated directory of embeddable fonts.
*
* @return string Raw PDF bytes.
*/
function buildCompactPdf(string $fontDirectory): string
{
// compress is true by default; pin it so the intent is explicit and the
// streaming writer path honours it regardless of any wrapper defaults.
$config = (new Config())
->withCompress(true)
->withFontsDirectory($fontDirectory)
// Bound the image cache so a build cannot exhaust memory. This is a
// memory ceiling, not an output-size control.
->withImageCacheBytes(16 * 1024 * 1024);
$doc = Document::createStandalone($config);
$doc->addFontDirectory($fontDirectory);
$doc->addPage();
// Rendering with an embeddable face records the used codepoints, which the
// writer turns into a font subset at build time.
$doc->setFont('LiberationSans', '', 12);
$doc->cell(0, 10, 'Invoice 2026', newLine: true);
$doc->cell(0, 10, 'Compressed streams plus an automatic font subset.', newLine: true);
// getPdfData() triggers the build: page streams and the subset font program
// are FlateDecode-compressed before the bytes are returned.
return $doc->getPdfData();
}
try {
$fontDirectory = resolveFontDirectory();
$pdf = buildCompactPdf($fontDirectory);
} catch (CompressionException $e) {
// Raised if the zlib encoder hard-fails while compressing a stream.
throw new RuntimeException(
sprintf('Compression failed for a %s stream.', $e->getAlgorithm()),
previous: $e,
);
} catch (InvalidConfigException $e) {
// Raised by the output path for an invalid destination configuration.
throw new RuntimeException(
sprintf('Output configuration "%s" was rejected.', $e->getConfigKey()),
previous: $e,
);
}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$path = $out !== false && $out !== '' ? $out : __DIR__ . '/small.pdf';
if (file_put_contents($path, $pdf) === false) {
throw new RuntimeException(sprintf('Could not write PDF to "%s".', $path));
}
printf("Wrote %d bytes to %s.\n", strlen($pdf), $path);

เอาต์พุต STDOUT ที่คาดหวัง (จำนวนไบต์ขึ้นอยู่กับฟอนต์และการสร้าง):

Wrote <n> bytes to <path>.
  • การบีบอัดเปิดอยู่ตามค่าเริ่มต้น Config ที่สร้างขึ้นใหม่มี compress ตั้งค่าเป็น true คุณแทบไม่จำเป็นต้องใช้ withCompress() เลย ตั้งค่าอย่างชัดเจนเพียงเพื่อบันทึกเจตนา หรือเพื่อปิดใช้ในการสร้างสำหรับดีบักเมื่อต้องการอ่านสตรีมดิบ
  • การปิดการบีบอัดทำให้ไฟล์ใหญ่ขึ้น ไม่ใช่เล็กลง withCompress(false) เป็นเครื่องมือวินิจฉัยสำหรับตรวจสอบสตรีมที่ไม่ได้บีบอัด ไม่เคยเป็นการปรับให้เหมาะสมด้านขนาด เมื่อนำส่งจริง ให้เปิดการบีบอัดไว้
  • การทำซับเซตต้องใช้ฟอนต์ที่ฝังจริง ฟอนต์มาตรฐาน Base14 (Helvetica, Times, Courier และตระกูลที่เกี่ยวข้อง) ถูกอ้างอิงด้วยชื่อและไม่ได้บรรจุโปรแกรมฟอนต์ที่ฝังในเอกสารธรรมดา จึงไม่มีอะไรให้ทำซับเซต การทำซับเซตลดขนาดได้เฉพาะแบบอักษรที่คุณฝังจากไฟล์ฟอนต์เท่านั้น
  • การทำซับเซตเกิดขึ้นอัตโนมัติและไม่มีการยืนยัน ไม่มีแฟล็ก ไม่มีเมท็อด และไม่มีการยืนยัน ถ้าคุณฝังฟอนต์และเรนเดอร์ข้อความด้วยฟอนต์นั้น ตัวเขียนได้ทำซับเซตให้แล้ว โปรแกรมที่ฝังมีแท็กคำนำหน้าซับเซตหกตัวอักษร (ตัวอย่างเช่น ABCDEF+LiberationSans) เพื่อให้ตัวอ่านแยกซับเซตออกจากการฝังแบบเต็มได้
  • การประหยัดเพียงเล็กน้อยจะคงฟอนต์เต็มไว้ เมื่อการทำซับเซตจะประหยัดได้น้อยกว่าสิบเปอร์เซ็นต์ของขนาดโปรแกรม ตัวทำซับเซตจะคืนต้นฉบับกลับมา นี่เป็นขีดจำกัดล่างที่จงใจ: ต้นทุนการสร้างใหม่ไม่คุ้มกับผลที่ได้เพียงเล็กน้อย การฝังแบบอักษรที่มีขนาดเล็กอยู่แล้ว หรือการเรนเดอร์กลิฟเกือบทั้งหมดของแบบอักษรนั้น อาจตกอยู่ในกรณีนี้ได้
  • imageCacheBytes ไม่ใช่ปุ่มปรับขนาดภาพ จำกัดหน่วยความจำ ไม่ใช่ไบต์เอาต์พุต NextPDF Core ฝังข้อมูลภาพที่คุณให้มา ไม่มีขั้นตอนการสุ่มตัวอย่างใหม่ การลดความละเอียด หรือการเข้ารหัสใหม่ ถ้าคุณต้องการภาพที่เล็กลง ให้ปรับขนาดและเข้ารหัสใหม่ก่อนฝัง
  • ไม่มีการตั้งค่าออบเจกต์สตรีมหรือการลดข้อมูลซ้ำ NextPDF Core ไม่เปิดให้ใช้สวิตช์เปิดปิดสำหรับออบเจกต์สตรีมของ PDF 2.0 หรือสำหรับการลดข้อมูลทรัพยากรที่ซ้ำกัน จึงไม่มีตัวเลือกดังกล่าวให้ใช้ ตัวควบคุมขนาดมีเพียงการบีบอัดสตรีมและการทำซับเซตฟอนต์

การบีบอัดที่ระดับ 9 เป็นส่วนที่ใช้ CPU หลักของการเขียนสตรีม โดยเพิ่มเวลาในการสร้างไม่กี่เปอร์เซ็นต์เพื่อแลกกับเอาต์พุตที่เล็กที่สุด ต้นทุนแปรผันตรงตามจำนวนไบต์ที่ยังไม่ได้บีบอัด ดังนั้นจำนวนหน้าและปริมาณข้อมูลฟอนต์ที่ฝังจึงเป็นตัวกำหนดงบประมาณ การทำซับเซตเพิ่มการประมวลผลครั้งเดียวต่อแบบอักษรที่ฝังหนึ่งตัว โดยแจงไดเรกทอรีตารางของฟอนต์ แก้ไขโคลสเชอร์ของกลิฟที่ใช้งาน และสร้างตารางที่จำเป็นขึ้นใหม่ สำหรับแบบอักษร CJK ขนาดใหญ่ นี่เป็นกลไกที่มีต้นทุนสูงกว่าในสองส่วนนี้ แต่ทำงานครั้งเดียวต่อฟอนต์ ไม่ใช่ครั้งเดียวต่อหน้า ขีดจำกัดล่างการประหยัดสิบเปอร์เซ็นต์มีอยู่ส่วนหนึ่งเพื่อกันการประมวลผลนั้นออกจากเส้นทางที่ถูกเรียกบ่อยเมื่อจะไม่คุ้มค่า เอกสารขนาดเล็กที่มีซับเซตที่ฝังหนึ่งชุดมักอยู่ภายในงบเวลา wall-clock 1500 ms และหน่วยความจำสูงสุด 96 MB ได้สบาย จำกัด imageCacheBytes ให้อยู่ในเพดานจริงของระบบของคุณ เพื่อให้การสร้างที่ฝังภาพจำนวนมากล้มเหลวเร็วด้วยข้อจำกัดหน่วยความจำแทนที่จะเข้าสู่การสว็อป

การสร้างทำงานภายในโพรเซส ไม่มีไบต์ของเอกสารออกจากโฮสต์และไม่มีการเรียกใช้เครือข่าย ถือว่าฟอนต์หรือภาพใด ๆ ที่มาจากภายนอกเป็นอินพุตที่ไม่น่าเชื่อถือ:

  • ตรวจสอบความถูกต้องของไดเรกทอรีฟอนต์ ตัวอย่างสำหรับการใช้งานจริงอ่านเส้นทางฟอนต์จากตัวแปรสภาพแวดล้อมที่เซิร์ฟเวอร์ควบคุม และปฏิเสธไดเรกทอรีที่หายไปหรืออ่านไม่ได้ก่อนฝัง ห้ามรับเส้นทางฟอนต์จากฟิลด์ของคำขอโดยเด็ดขาด
  • ฝังเฉพาะฟอนต์ที่คุณได้รับอนุญาตให้แจกจ่ายซ้ำ ซับเซตยังคงเป็นโปรแกรมฟอนต์แบบฝัง ยืนยันว่าใบอนุญาตอนุญาตให้ฝังได้ก่อนที่คุณจะนำส่งเอกสารที่บรรจุแบบอักษรนั้น
  • ฟอนต์ที่มีรูปแบบไม่ถูกต้องจะยกข้อยกเว้น ไม่ได้ทำให้เสียหายอย่างเงียบ ๆ ไฟล์ฟอนต์ที่แยกวิเคราะห์ไม่สำเร็จจะยก NextPDF\Exception\FontParsingException และความล้มเหลวร้ายแรงของ zlib จะยก NextPDF\Exception\CompressionException ดักจับข้อยกเว้นที่เจาะจงที่สุดและจัดการข้อยกเว้นนั้น อย่าห่อการสร้างไว้ในบล็อก catch ที่ว่างเปล่าเด็ดขาด
  • อย่าแทรกค่าอินพุตของผู้ใช้เข้าไปในเส้นทางเอาต์พุตเด็ดขาด ตัวอย่างเขียนไปยังเส้นทางที่กำหนดตายตัวหรือช่องทางแยกที่เซิร์ฟเวอร์ควบคุม และปฏิเสธ stream wrapper และไบต์ว่าง (null byte) ผ่านตัวเขียนแบบอะตอมมิกใน save() ให้เส้นทางเอาต์พุตมาจากค่าที่เซิร์ฟเวอร์ควบคุมเพื่อหลีกเลี่ยงการเจาะผ่านเส้นทาง (path traversal)
  • ห้ามมีความลับในเอกสาร อย่าฝังข้อมูลรับรอง โทเค็น หรือตัวระบุภายในไว้ในเอกสารที่สร้างขึ้นซึ่งคุณส่งคืนให้ไคลเอนต์

เรซิพีนี้ไม่ได้อ้างว่าเป็นไปตามมาตรฐานบังคับใด ๆ ด้วยตัวเอง กลไกที่ใช้ถูกกำหนดไว้ในข้อกำหนด PDF 2.0: การบีบอัดสตรีม FlateDecode (ISO 32000-2:2020 §7.4.4) และการตั้งชื่อซับเซตฟอนต์ด้วยคำนำหน้าซับเซตหกตัวอักษร (ISO 32000-2:2020 §9.9) NextPDF สร้างทั้งสองอย่างเป็นส่วนหนึ่งของเส้นทางการเขียนมาตรฐาน คุณไม่ได้กำหนดค่าใด ๆ นอกเหนือจากแฟล็ก compress โปรไฟล์การทำซ้ำได้แบบ structural ที่หน้านี้ประกาศสะท้อนว่าตัวเขียนตรึงระดับ DEFLATE ไว้ ดังนั้นสตรีมที่บีบอัดจึงกำหนดได้แน่นอน ขณะที่ตัวระบุระดับเอกสารอาจยังต่างกันระหว่างการรัน เว้นแต่คุณจะกำหนดการตั้งค่าแบบกำหนดได้แน่นอนด้วย สำหรับกลไกการฝังที่อยู่เบื้องหลังการทำซับเซต โปรดดูเรซิพี embed-and-subset ที่ลิงก์ไว้ด้านล่าง