วิธีตั้งความกว้างคอลัมน์ Excel ให้พอดีกับข้อความภาษาไทย

PhpSpreadsheet เดาความกว้างคอลัมน์จากจำนวนตัวอักษร ซึ่งนับสระบนล่างกับวรรณยุกต์ไทยเป็นตัวด้วย คอลัมน์ภาษาไทยจึงกว้างเกินจริง ส่วน OpenSpout ไม่วัดให้เลย ทางที่นิ่งกว่าคือกำหนดความกว้างจากชื่อหัวคอลัมน์ ใช้ฟอนต์ที่มีอยู่ในเครื่องคนเปิด และตั้งชื่อชีทไทยให้ไม่เกิน 31 ตัว

เผยแพร่
เวลาอ่าน
5 นาที
ในโพสต์นี้ 7 หัวข้อ
  1. PhpSpreadsheet เดาความกว้างจากจำนวนตัวอักษร
  2. สระบนล่างและวรรณยุกต์ไทยนับเป็นตัวอักษรด้วย
  3. OpenSpout ให้ตั้งความกว้างเองทุกคอลัมน์
  4. กำหนดความกว้างจากชื่อหัวคอลัมน์แทนการวัด
  5. ฟอนต์ไม่ได้ฝังไปกับไฟล์ Excel
  6. ชื่อชีทภาษาไทยยาวได้ไม่เกิน 31 ตัว
  7. ความกว้างที่เพี้ยนบ่อยในรายงานจริง

รายงานที่ export ออกมาเป็นภาษาไทยมักมีอาการคุ้น ๆ อยู่สองแบบ คอลัมน์ชื่อสินค้ากว้างจนต้องเลื่อนจอ หรือคอลัมน์ยอดเงินแคบจนเห็นเป็น ##### ส่วนอีกอาการหนึ่งเกิดตอนส่งไฟล์ไปให้คนอื่น เปิดในเครื่องเราสวย แต่ในเครื่องฝ่ายบัญชีฟอนต์กลายเป็นอีกแบบ และตัวหนังสือล้นช่อง

ทั้งสามเรื่องมาจากที่เดียวกัน ไฟล์ .xlsx เก็บแค่ตัวเลขความกว้างกับชื่อฟอนต์ ไม่ได้เก็บว่าข้อความในช่องยาวเท่าไหร่ และไม่ได้เก็บตัวฟอนต์ไปด้วย

PhpSpreadsheet เดาความกว้างจากจำนวนตัวอักษร

ความกว้างคอลัมน์ใน Excel มีหน่วยเป็นจำนวนตัวเลขของฟอนต์มาตรฐานที่วางเรียงกันได้ในช่อง ค่าเริ่มต้นคือ 8.43 ตัว Excel เองจะ autofit ให้ก็ต่อเมื่อเราดับเบิลคลิกเส้นแบ่งคอลัมน์ ตอนเปิดไฟล์จะใช้ตัวเลขที่เก็บไว้เสมอ

PhpSpreadsheet จึงต้องคำนวณเองตอนเราสั่ง setAutoSize(true) (Laravel Excel ใช้คำสั่งเดียวกันนี้เมื่อ export class มี ShouldAutoSize) วิธีที่ใช้เป็นค่าเริ่มต้น เรียกว่า approx คือนับตัวอักษรด้วย mb_strwidth() แล้วคูณ 8.26 pixel ต่อตัวสำหรับ Calibri ขนาด 11 แล้วปรับตามขนาดฟอนต์ ไม่ได้วัดรูปตัวอักษรจริง

อีกวิธีคือ exact ซึ่งวัดจากไฟล์ฟอนต์ด้วย GD แต่ต้องตั้ง Font::setTrueTypeFontPath() ให้ชี้ไปที่โฟลเดอร์ฟอนต์บนเซิร์ฟเวอร์ และรายชื่อฟอนต์ที่ PhpSpreadsheet รู้จักมีแต่ฟอนต์ละติน (Arial, Calibri, Tahoma และอื่น ๆ) ฟอนต์ไทยอย่าง TH Sarabun ต้องเพิ่มเองผ่าน Font::setExtraFontArray() บนเซิร์ฟเวอร์ Linux ทั่วไปก็มักไม่มีไฟล์ฟอนต์พวกนี้อยู่แล้ว ทางนี้จึงแทบไม่มีใครใช้

สระบนล่างและวรรณยุกต์ไทยนับเป็นตัวอักษรด้วย

mb_strwidth() นับทุก code point ที่ไม่ใช่ตัวกว้างพิเศษเป็น 1 ช่อง สระบน สระล่าง และวรรณยุกต์ไทยจึงได้ช่องของตัวเองด้วย ทั้งที่บนจอวางซ้อนอยู่บนพยัญชนะ ไม่ได้กินที่ด้านข้าง

ข้อความ PhpSpreadsheet นับ ช่องที่เห็นจริง
ยอดขายรวม 9 9
สาขาลาดพร้าว 12 11
น้ำที่ไม่ได้ใช้ 15 8

คำที่มีวรรณยุกต์กับสระบนล่างเยอะจึงได้คอลัมน์กว้างเกินเกือบเท่าตัว ส่วนคำที่แทบไม่มีเลยก็นับใกล้เคียงของจริง ในคอลัมน์ที่ข้อความปนกัน ความกว้างจึงขึ้นกับว่าแถวที่ยาวที่สุดเป็นคำแบบไหน

อีกเรื่องที่ซ้อนอยู่คือ Calibri ไม่มีตัวอักษรไทย Excel จะดึงฟอนต์ไทยในเครื่องมาแสดงแทน ซึ่งตัวกว้างไม่เท่า Calibri อยู่แล้ว ตัวคูณ 8.26 จึงไม่ตรงกับภาษาไทยตั้งแต่ต้น

OpenSpout ให้ตั้งความกว้างเองทุกคอลัมน์

OpenSpout เขียนไฟล์ทีละแถวแล้วปล่อยแถวนั้นออกจาก memory (เหตุผลอยู่ในตอนเรื่อง memory) จึงไม่มีข้อมูลทั้งคอลัมน์ให้วัด และไม่มีคำสั่ง autosize เลย เราต้องบอกความกว้างเองผ่าน Options ของ writer หรือของแต่ละชีท

จุดที่ควรรู้คือความกว้างที่ตั้งผ่าน Options ใช้กับทุกชีทในไฟล์ ถ้าไฟล์มีหลายชีทที่คอลัมน์ไม่เหมือนกัน ให้ตั้งที่ตัวชีทแยกทีละชีท และตั้งทีละคอลัมน์ อย่าให้ช่วงคอลัมน์ทับกัน เพราะ Excel อ่านช่วงที่ทับกันแล้วสับสน

กำหนดความกว้างจากชื่อหัวคอลัมน์แทนการวัด

ระบบที่มีรายงานหลายสิบตัวมักลงเอยที่ทางเดียวกัน คือเลิกวัดแล้วกำหนดความกว้างเป็นตาราง หัวคอลัมน์ชื่อเดียวกันได้ความกว้างเท่ากันทุกรายงาน ไม่ว่าจะเขียนด้วย PhpSpreadsheet หรือ OpenSpout

private const array WIDTHS = [
    'รหัสลูกค้า' => 14,
    'ชื่อลูกค้า' => 40,
    'วันที่เอกสาร' => 12,
    'ยอดเงิน' => 16,
    'หมายเหตุ' => 50,
];

foreach ($headings as $index => $heading) {
    $column = Coordinate::stringFromColumnIndex($index + 1);

    $sheet->getColumnDimension($column)->setWidth(self::WIDTHS[$heading] ?? 14);
}

หลักที่ใช้ตั้งตัวเลขในตาราง

  • คอลัมน์ตัวเลข กว้างตามค่าที่ยาวที่สุดหลังจัดรูปแบบแล้ว ไม่ใช่ตามหัวคอลัมน์ ยอด 1,234,567.89 มี 12 ตัว บวกที่เผื่ออีกนิด ถ้าแคบกว่านั้น Excel จะแสดง ##### แทนตัวเลข
  • คอลัมน์ข้อความยาว เช่นหมายเหตุหรือที่อยู่ ตั้งกว้างพอประมาณแล้วเปิด wrap text ดีกว่าปล่อยให้กว้างตามแถวที่ยาวที่สุด
  • หัวคอลัมน์ยาวกว่าข้อมูล เช่น "วันที่ส่งคำขอ" ที่ข้อมูลเป็นวันที่สั้น ๆ ให้ wrap หัวคอลัมน์แทนการขยายทั้งคอลัมน์

ไฟล์เล็ก ๆ ที่ไม่กี่ร้อยแถวยังใช้ autosize ได้ แต่กับไฟล์ใหญ่ การวัดต้องวนทุกช่องในคอลัมน์ ช้าลงตามจำนวนแถวด้วย

ฟอนต์ไม่ได้ฝังไปกับไฟล์ Excel

ไฟล์ .xlsx เก็บแค่ชื่อฟอนต์ ไม่ได้เก็บตัวฟอนต์ Microsoft เปิดให้ฝังฟอนต์ได้ใน Word กับ PowerPoint แต่ไม่มีใน Excel ถ้าเครื่องที่เปิดไม่มีฟอนต์ที่ระบุ Excel จะใช้ฟอนต์อื่นแทน ความกว้างที่เราตั้งไว้พอดีก็เพี้ยนไปด้วย

TH Sarabun New เป็นฟอนต์ที่หนังสือราชการใช้เป็นหลัก แต่ไม่ได้ติดมากับ Windows หรือ macOS คนที่ไม่เคยลงไว้จะเห็นเป็นฟอนต์อื่น ถ้ารายงานไม่ได้บังคับฟอนต์ ให้เลือกฟอนต์ที่มีในเครื่องส่วนใหญ่ เช่น Tahoma แล้วตั้งเป็นฟอนต์ของทั้งไฟล์ทีเดียว

// PhpSpreadsheet
$spreadsheet->getDefaultStyle()->getFont()->setName('Tahoma')->setSize(10);

// OpenSpout
$options = new Options(new Style(fontSize: 10, fontName: 'Tahoma'));

ถ้าเอกสารต้องออกมาเป็นฟอนต์นั้นเป๊ะ ๆ ทุกเครื่อง เช่นแบบฟอร์มที่ส่งหน่วยงาน ไฟล์ Excel ไม่ใช่ตัวเลือกที่เหมาะ ทำเป็น PDF ที่ฝังฟอนต์ไว้จะแน่นอนกว่า วิธีทำอยู่ในโพสต์เรื่อง PDF ภาษาไทยใน Laravel

ชื่อชีทภาษาไทยยาวได้ไม่เกิน 31 ตัว

ชื่อชีทมีกฎของ Excel ที่ library บังคับตามด้วย

  • ยาวได้ไม่เกิน 31 ตัว และสระบนล่างกับวรรณยุกต์ไทยก็นับเป็นตัวเหมือนตอน autosize ชื่ออย่าง "สรุปยอดขายสาขาลาดพร้าวประจำเดือน" จึงเกินโดยไม่รู้ตัว
  • ห้ามมี / \ ? * : [ ] ชื่อที่มีวันที่แบบ 10/2569 จึงใช้ไม่ได้
  • ห้ามขึ้นต้นหรือลงท้ายด้วย ' และห้ามตั้งชื่อว่า History เพราะ Excel สงวนไว้ใช้เอง OpenSpout ปฏิเสธชื่อนี้ แต่ PhpSpreadsheet ยอมให้ตั้ง จึงต้องกันเอง

ถ้าชื่อชีทมาจากข้อมูล เช่นชื่อสาขาหรือชื่อลูกค้า ให้ตัดตัวต้องห้ามออกแล้วตัดความยาวก่อนส่งให้ library

$title = Str::of($branchName)->replaceMatches('#[/\\\\?*:\[\]]#', '-')->trim("'")->substr(0, 31)->toString();

substr() ของ Str ตัดตามตัวอักษร ไม่ใช่ตาม byte ภาษาไทยจึงไม่ขาดกลางตัว แต่อาจตัดวรรณยุกต์ออกจากพยัญชนะตัวสุดท้ายได้ ถ้าชื่อเสี่ยงจะยาว ตั้งชื่อชีทสั้น ๆ เองแล้วใส่ชื่อเต็มเป็นหัวรายงานในชีทแทนจะอ่านง่ายกว่า

ความกว้างที่เพี้ยนบ่อยในรายงานจริง

ตอนที่เราทำระบบรายงานจาก SAP ที่ส่ง Excel ให้แต่ละฝ่ายตามเวลา รายงานมีหลายสิบตัว เราก็ต้องคิดตั้งแต่แรกว่าหัวคอลัมน์ชื่อเดียวกันควรกว้างเท่ากันทุกไฟล์ จึงเลือกใช้ตารางความกว้างกลางแทน autosize ข้อดีที่ได้ตามมาคือรายงานที่ย้ายไปเขียนด้วย OpenSpout ก็ใช้ตารางเดียวกันได้เลย

ตารางแบบนี้ก็มีกับดักของตัวเอง ถ้าจับคู่ชื่อหัวคอลัมน์แบบหลวม ๆ (คำว่า "no" ไปตรงกับ "Customer Note") หรือคอลัมน์เว้นว่างที่ไม่มีหัว ก็จะได้ความกว้างผิดคอลัมน์ ให้จับคู่ชื่อเต็มก่อน แล้วค่อยใช้ค่าเริ่มต้นกับคอลัมน์ที่ไม่มีในตาราง

ตอนถัดไปของซีรีส์ Export Excel และ CSV จาก Laravel

อ่านต่อ

ถูกอ้างถึงใน

ล่าสุด

ดูทั้งหมด →