Site Canvas

【程式】使用JavaScript在中文與英文間墊上空格

中文文字工作者應該都聽過這個排版準則:

中英文交界處必須增加一個空格,避免視覺上過於壅擠[1]。

在部分的軟體或系統中,會自動墊上這樣的空間(包含你現在正看的這篇文章,以及諸如蘋果與Google的作業系統[2][3]);然而,並不是所有的讀寫介面都內建有這樣的功能。根據筆者實測,在2020年3月,Facebook的iPhone應用會自動墊上這個空間;但是在Web中則沒有——前者亦不確定是來自iPhone的作業系統,抑或是APP本身的功能。

中英文交錯的排版圖片:上圖中中英文沒有空間,顯得擁擠;下圖中則添加了半行空格。你覺得有差嗎⋯⋯可以確定的是,有人覺得有差。

所以⋯⋯我們要手動補上空白嗎

儘管這個空白是很常見的需求。然而,就筆者的觀點來看。在文本原始檔中「手動」添加空格,並不是一個理想的做法。其原因如下:

  • 非常容易疏漏。
  • 不同字體可能有不同適合的空間。
  • 在文本的原始碼中加入非必要的空格會讓管理複雜化,增加修訂、編輯與校對的負擔。
  • 以上,如果文本需要長期維護與修改,則花費的精力與效果不成比例。

讓程式來做吧!

但如果我們還是希望達成最舒適的排版,要怎麼做呢?

個人覺得最好的作法就是在閱讀的輸出端,利用程式自動墊上這個空間。

以本篇部落格為例,當訪客在讀取頁面時,筆者啟動了一段JavaScript,會將頁面中部分區塊的內容進行自動化處理。也因此,部落格中所有的文章,中英文間都會自動墊上一個半形空格。

該功能的實作如下:

function getPaddedCnEnString(htmlStr, padding) {
  // ASCII 33~126
  const EN_ALPHA = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
  const EN_SET = new Set(EN_ALPHA.split(""));
  const CN_MARKS = "。,、;:「」『』()—-?!——⋯⋯〈〉・《》_~";
  const CN_MARKS_SET = new Set(CN_MARKS.split(""));
  const CN_RANGE_MIN = 0x3400;
  const CN_RANGE_MAX = 0x9FFC;
  // utility function
  const isCn = (char) => ((char.charCodeAt(0) >= CN_RANGE_MIN && char.charCodeAt(0) <= CN_RANGE_MAX) ||
    CN_MARKS_SET.has(char));
  const isEn = (char) => EN_SET.has(char);
  // process
  let result = "";
  let thisIdx = 0;
  while (thisIdx <= htmlStr.length) {
    const thisChar = htmlStr.charAt(thisIdx);
    let nextIdx = thisIdx + 1;
    let nextChar = htmlStr.charAt(nextIdx);
    // copy contents in html tags (do no processing)
    if (thisChar === "<") {
      while (nextIdx <= htmlStr.length && htmlStr[nextIdx - 1] !== ">") {
        nextIdx++;
      }
      result += htmlStr.substring(thisIdx, nextIdx);
      thisIdx = nextIdx;
      continue
    } else if (nextChar === "<") {
      while (nextIdx <= htmlStr.length && htmlStr[nextIdx - 1] !== ">") {
        nextIdx++;
      }
    }
    result += htmlStr.substring(thisIdx, nextIdx);
    nextChar = htmlStr.charAt(nextIdx);
    // compare with next char and pad space if necessary
    if (isCn(thisChar) && isEn(nextChar)) { // 中文_abc
      result += padding;
    } else if (isEn(thisChar) && isCn(nextChar)) { // abc_中文
      result += padding;
    }
    // continue
    thisIdx = nextIdx;
  }
  return result;
}
  • 函式的輸入為:
    • htmlStr:字串,可包含HTML標籤(HTML標籤中的屬性不會被處理)
    • padding:你可以使用半形空白" "或是例如<span>標籤,再透過CSS來調整)
  • 函式使用Unicode判斷字元屬於中文還是英文,並將台灣官方的標準標點符號視做中文[4]。以此為判斷,自動在中英文間加上空間。

舉例來說,如果你的文章位於<div id="post">的標籤裡,那麼你可以這麼做[5]

$(document.onload).ready(function () {
  const element = $("#post");
  const newHTML = getPaddedCnEnString(element.html()," ");
  element.html(newHTML);
})

根據筆者簡單的測試,一份原始碼長5000字左右的markdown文章,以其HTML格式進行轉換,可以控制在5毫秒以內;且不妨礙原本內容的讀取;顯然不會造成太大的效能負擔。

以上,獻給排版的偏執狂們。


註解

[1] 儘管就筆者所知,這樣的準則並沒有被明文記載在各大軟體廠的設計文件裡。此外,筆者也反對將這樣的標準用於判斷寫作者是否專業;亦不建議用手動處理這個程序:"The space bar is not a design tool"。

[2] 出處:知乎|https://www.zhihu.com/question/19587406

[3] 非正規的指南例如:Tunghsiao Liu|中文文案排版指北|GitHub。這份文件在2020年3月時累計了7500個GitHub星星。

[4] 在Liu的指南中主張中文標點與英文間不需額外墊上空格。這其實很主觀,但我們的函示仍然提供了設定的參數。

[5] 文中預設使用jQuery在前端執行轉換。如果你可以控制後端的原始碼,也可以將轉換放在伺服器端進行。另外,如果放在前端且你有使用其他JS工具去處理HTML(比如說程式碼標註工具),那麼你可能需要注意執行的先後順序;或是採用適當的CSS Selector去跳過不需要處理的區塊。

Leave a Reply