レシートの両端揃え

レシートを印刷する

文具店

2025-05-01 10:00:00                端末取引ID:23
                                スタッフ:テスト
万年筆                                    ¥5,000
                         小計             ¥5,000
                           税               ¥500
                         合計             ¥5,500
                                    No.123456789

縦に位置を揃えている

文具店

2025-05-01 10:00:00                端末取引ID:23
                                スタッフ:テスト
万年筆                                    ¥5,000
                         小計             ¥5,000
                           税               ¥500
                         合計             ¥5,500
                                    No.123456789

半角スペースを入れて調整している

HTMLのようなテーブルや回り込みはない。

文字幅を計算して必要な半角スペースを入れている。

万年筆                                    ¥5,000

文字幅はバイト数から計算できる

日本語はShift-JISでエンコードしている。

Shift-JISでは、全角文字は2バイト、半角文字は1バイト。

半角文字の幅を1とするとバイト数と文字幅が一致する。

万年筆 (3文字 × 2バイト) = 6バイト = 6幅

Shift-JISでは外国語の文字が印字できない

à

  • aにグレイヴ・アクセントを付けた文字。

  • フランス語、イタリア語で使われる。

œ

  • oとeが合わさった文字。

  • フランス語で使われる。

エンコードを変更することで印字できる

外国語に対応しているエンコードに変更することで印字できる。

→ 日本語と外国語が混在できない。

Unicodeであれば併用できる

Unicode (UTF-8) に対応したプリンターが登場。

  • スター精密
    • mC-Printシリーズ
    • FVP10 (ファームウェアVer1.5以降/インターフェースVer3.2.0以降)
    • TSP650II (ファームウェアVer3.0以降/インターフェースVer3.2.0以降)
  • エプソン
    • TM-m30II/TM-m30IIIシリーズ

文字幅はバイト数と一致しない

  • アルファベットなど → 1バイト
  • ギリシャ文字、キリル文字、円記号など → 2バイト
  • ひらがな、カタカナ、漢字など → 3バイト
  • 絵文字など → 4バイト

バイト数で計算するとずれる

日本語部分が3バイト、「¥」が2バイトのためずれる。

万年筆                                ¥5,000    

1. Shift-JISに変換してバイト数を使用する

  • Unicode文字をShift-JISに変換してバイト数を計算する
  • 変換できない文字は1バイトとして扱う

→ Shift-JISに存在しない全角文字が多数あるため不正確。

拡張漢字、絵文字、特殊記号、全角記号など

2. フォントデータから取得する

フォントデータ (.ttf, .otf)) には文字の情報も含まれている。

  • フォントを描画する際に必要な幅
  • フォントの高さ
  • フォントの太さ
  • etc.

2. 専用フォントはデータが取得できなかった

レシートプリンターで使用されるフォントは専用のもの。

  • Font A (12x24ドット)
  • Font B (9x17ドット)

→ 探した限りではダウンロードできるデータはなかった。

3. 東アジアの文字幅の特性値から計算する

東アジアの文字幅というUnicode標準の付属書がある。

従来の文字コードからの移行時に互換性を保つために定義された。

慣習的な文字幅に合わせた特性が割り当てられている。

3. 東アジアの文字幅特性は6種類に分類される

F (Fullwidth; 全角)

互換分解特性 を持つ互換文字。

全角英数など。

H (Halfwidth; 半角)

互換分解特性 を持つ互換文字。

半角カナなど。

3. 東アジアの文字幅特性は6種類に分類される

W (Wide; 広)

互換文字以外のいわゆる全角であったもの。

ひらがな、カタカナ、漢字、句読点など。

Na (Narrow; 狭)

互換文字以外の対応するいわゆる全角の文字が存在したもの。

半角英数など。

3. 東アジアの文字幅特性は6種類に分類される

A (Ambiguous; 曖昧)

文脈によって文字幅が異なる文字。

ギリシア文字やキリル文字など。

N (Neutral; 中立)

全角でも半角でもない文字。

アラビア文字など。

3. 東アジアの文字幅特性は6種類に分類される

Shift-JIS 約 8,000 文字
Unicode 約 150,000 文字

F (Fullwidth) 約 1,800 文字
W (Wide) 約 87,000 文字
H (Halfwidth) 約 600 文字
Na (Narrow) 約 10,000 文字
A (Ambiguous) 約 6,000 文字
N (Neutral) 約 44,000 文字

3. 東アジアの文字幅特性から文字幅を推定する

曖昧 (A) と中立 (N) は実際に印字してみるしかない。

3. 文字幅特性が曖昧 (A) なもの

半角

ラテン文字 à
分数 ½

全角

ギリシャ文字 α
キリル文字 Ж
罫線 ┼
数学記号 ∑
セクションマーク §
パーミル ‰
オーム Ω
ローマ数字 Ⅻ
矢印 ↔

3. 文字幅特性が中立 (N) なもの

半角

ヘブライ文字 א
マイクロ µ
オングストローム Å

印字なし (フォント未対応)

音声記号 ʃ
絵文字 💙
絵文字 🔟

3. 文字幅特性だけ文字幅は推定できない

4. 複合的に判断する

完璧に判定する方法はないので妥協する。

  • Shift-JISに変換できる文字 → Shift-JISのバイト数を使用
  • 上記以外で文字幅特性がF, W → 全角
  • 上記以外で文字幅特性がH, Na, A, N → 半角

一部の文字は対応できていない

一部の文字は文字幅を小さく計算される。

Shift-JISに存在しないが全角で印字されるもの。

  • ローマ数字 Ⅻ
  • 矢印 ↔

ライブラリを探す

https://github.com/ukitaka/EastAsianWidth.swift

public extension UnicodeScalar {
  public var isEastAsianWide: Bool {
    switch self.value {
    case 0x1100...0x115F: return true
    ...
    }
  }

  public var isFullwidth: Bool {
    return isEastAsianFullwidth || isEastAsianWide
  }
}

コードに反映する

extension String {
  func countByEastAsianWidth() -> Int {
    func accumulate(count: Int, unicodeScalar: UnicodeScalar) -> Int {
      if String(unicodeScalar).lengthOfBytes(using: .shiftJIS) > 0 {
        return count + String(unicodeScalar).lengthOfBytes(using: .shiftJIS)
      } else {
        return count + (unicodeScalar.isFullwidth ? 2 : 1)
      }
    }
    return self.unicodeScalars.reduce(0, accumulate)
  }
}

実装だけを見ると簡単にも見える

日本語のみの時は実現できている。

コードの変更量も少ない。

途中の調査・検証は難しい

調査・検証には多くの時間がかかっている。

事前には知識が足りていないため見積もりも難しい。

妥協するポイントを決めるのも難しい

完璧に実現するのはとても難しい。

どこで妥協するかを決める必要がある。

以上