LoginSignup
3
1

More than 5 years have passed since last update.

指定dotの空白を生成する

Last updated at Posted at 2017-11-30

趣味でJavaScriptを使ってAAエディタを作っているのだが、そこで使う指定dotの空白を生成する関数を作った。絶対間違ってそうなのでAdvent Calenderとして晒し上げる。
基準は本来16pxのMS Pゴシックだが、WebkitだとDirectWriteの影響でAAが表示されないため計測にはSaitamaarを使う。

AAで使う空白

Shift-JISの空白

Shift-JISでは、空白は以下のものしかない。

名前 記号 dot数
全角スペース [ ] 11dot
半角スペース [ ] 5dot

さらに2ちゃんねる系掲示板では以下の制約がある

  • 半角スペースの行頭禁止(自動的に削除される)
  • 半角スペースの連続禁止(自動的に1個にされる)

当然この条件では自由な空白が作れない。そこでピリオド.(3px)なども使いある程度自由に作っていたが、現在はやる夫系掲示板の多くでは容量と引き換えにUnicode空白の使用が可能である。

Unicode空白

Unicodeでは、以下の空白が使用可能である。

名前 記号 ドット数
EM SPACE [ ] 16dot
FIGURE SPACE [ ] 10dot
EN SPACE [ ] 8dot
THREE-PER-EM SPACE [ ] 5dot
FOUR-PER-EM SPACE [ ] 4dot
SIX-PER-EM SPACE [ ] 3dot
THIN SPACE [ ] 2dot
HAIR SPACE [ ] 1dot

ただし、Unicodeは通常は掲示板に送信できないので、& #x2006;という形の文字参照で送る。すると、1文字につき12byteほど使うことになる。となると、1レスのbyte数に引っかかったりするので、出来るだけUnicode空白は少なくしたい。

条件

  • 半角スペースは5dot、全角スペースは11dot。
  • 半角スペースの連続禁止。
  • 半角スペースが頭に来るの禁止。
  • Unicode空白使用可能。ただし可能な限り少なくすること。
  • できれば速度。レイヤー処理とか書くと何回も呼びだすので。

実装

lib/space.js
// 大分省略したもの。全体はhttps://github.com/Duct-and-rice/misaki-editor/blob/master/src/lib/space.js。
export function generateSpaceFromAH (a, h) {
    if (a === 0 && h === 1) {
        return HALF_SPACE
    }
    if (a < h || a < 0 || h < 0) {
        throw new Error('a:' + a + ',h:' + h)
    }
    return DOTS_TO_SPACE[11].str.repeat(a - h) +
        (DOTS_TO_SPACE[11].str + HALF_SPACE).repeat(h) // a - h個の全角空白とh個の全角空白半角空白の連続文字列を並べる。
}

export function oneDotReduce (ah) {
    ah = (() => {
        if (ah.a - 1 >= ah.h + 2) {
            return {a: ah.a - 1, h: ah.h + 2, adj: ah.adj}
        } else if (ah.adj >= 1) {
            return {a: ah.a, h: ah.h, adj: ah.adj - 1}
        } else if (ah.h >= 1) {
            return {a: ah.a, h: ah.h - 1, adj: ah.adj + 4}
        } else if (ah.a >= 1 && ah.adj < 6) {
            return {a: ah.a - 1, h: ah.h + 1, adj: ah.adj + 5}
        }
    })()
    if (ah.h >= 1 && ah.adj <= 6) {
        ah.h--
        ah.adj += 5
    }
    return ah
}

export default function widthSpace (sp) {
    if (typeof sp !== 'number') {
        throw new TypeError()
    }
    const mod = sp % 11
    let a = 0 // 全角スペースの数
    let h = 0 // 半角スペースの数
    let adj = 0 // 調整用dot
    switch (mod) {
    case 0:
        a = Math.floor(sp / 11)
        break
    case 1:
    case 2:
    case 3:
    case 4:
        a = Math.floor(sp / 11)
        h = 1
        break
    case 5:
        if (sp === 5) {
            return DOTS_TO_SPACE[5].str
        }
        a = Math.floor(sp / 11)
        h = 1
        break
    case 6:
    case 7:
    case 8:
    case 9:
    case 10:
        a = Math.ceil(sp / 11)
        break
    }

    if (mod !== 0 && mod !== 5) {
        while (a * 11 + h * 5 + adj !== sp) {
            const ah = oneDotReduce({a, h, adj})
            a = ah.a
            h = ah.h
            adj = ah.adj
        }
        const ah = adjToAH({a, h, adj})
        a = ah.a
        h = ah.h
        adj = ah.adj
    }

    return generateSpaceFromAH(a, h) + adjustWithUnicode(adj)
}

まず指定dotから少し多めに全角スペースと半角スペース1or0だけで軽く見積る。
たとえば100dotの場合、全9、半1で11 * 9 + 5 * 1 = 104となる

次に、それを1dotずつ減らしていく。減らすには全角スペースを1つ減らし半角スペースを2つ増やす。すると-11 * 1 + 5 * 2 = -11 + 10 = -1で一つ減る。この処理は上のoneDotReduce関数で行なっている。

当然、全2半1など対応できないケースが出てくるので、その時に前述のUnicode空白を用いて調整した。どこか間違っているかも……

あとはそれらを半角スペースの制約に引っかからないように文字列化する。全角スペースをA、半角スペースをHとすると、AHAHAHAHというように半角スペースを配置すれば制約に引っかからないので、AAAAHAHAHAHというようなスペースを目指す関数がgenerateSpaceAHである。

あとは最後に調整用Unicode空白を付けて終わり!閉廷!

やばそうなところ

  • oneDotReduceがちゃんと動くか
  • 速度(多分チューニングしてもJSの壁にぶちあたるので出来ればWASM化したい)
  • この記事を書いていて気づいたのだが5dotの場合行頭がそうでないかで場合分けが必要では?

コード全体

参考文献

  1. 『Orinrin Editor』紹介 第02回 『Orinrin Editor』で「ユニコード空白」を使ってみよう - やる夫まとめもZ - http://matomemo3.blog.fc2.com/blog-entry-5263.html
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1