Rustで.ttcファイルを1つ以上の.ttfファイルへ変換しよう!

 Rustのeguiで日本語を表示するには.ttfファイルを指定する必要が有るのですが、.ttcファイルしかないフォントが多いので、.ttcファイルを1つ以上の.ttfファイルへ変換するアプリケーションを作ってみました。

 ただし、.ttfファイルが正しいかどうか確認する方法が分からないので、アプリケーションの動作が正しいかどうか確認できていません。


 不具合が有るかもしれないので利用は自己責任でお願いいたします。


Cargo.toml

――――――――――――――――――――

[package]

name = "ttc_to_ttf"

version = "0.1.0"

edition = "2021"


[dependencies]

regex = "1"

eframe = "0.16"

dirs = "4.0"

――――――――――――――――――――


main.rs

――――――――――――――――――――

use std::fs::File;

use std::io::BufReader;

use std::io::Read;

use std::io::Seek;

use std::io::SeekFrom;

use std::io::BufWriter;

use std::io::Write;

use regex::Regex;

use eframe::epi::App;

use eframe::egui::CtxRef;

use eframe::epi::Frame;

use eframe::epi::Storage;

use eframe::egui::Vec2;

use eframe::egui::FontDefinitions;

use eframe::egui::FontData;

use eframe::egui::FontFamily;

use eframe::egui::CentralPanel;

use eframe::NativeOptions;

use std::path::PathBuf;


fn ttc_to_ttf(ttc: &str, out_dir: &str) -> Vec<String> {


  let mut buf_reader = BufReader::new(File::open(ttc).unwrap());


  //.ttcファイルはビッグ エンディアン(ネス)


  //TTC Headerを読み込んでいく


  let mut byte_array: [u8; 4] = [0; 4];


  //4バイトのASCII文字列のTTC Tagを読み込みUTF-8へ変換

  //"ttcf"

  buf_reader.read(&mut byte_array).unwrap();

  //let ttc_tag = String::from_utf8(byte_array.to_vec()).unwrap();


  //4バイトのVersionを読み込み16進数表記のUTF-8文字列へ変換

  //"00010000"か"00020000"

  //"00010000"はTTC Header Version 1.0を意味

  //"00020000"はTTC Header Version 2.0を意味

  buf_reader.read(&mut byte_array).unwrap();

  //let version = byte_array.iter().map(|byte| format!("{:>02x}", byte)).collect::<Vec<String>>().join("");


  //4バイトのnumFontsを読み込み整数へ変換

  buf_reader.read(&mut byte_array).unwrap();

  let num_fonts = u32::from_be_bytes(byte_array);


  let mut offset_vec: Vec<u64> = Vec::new();

  //numFontsの数だけ、くり返し

  for _number in 1..(num_fonts + 1) {

    //4バイトの各フォントのデータの開始バイトを読み込みへ整数へ変換

    //0バイト目から開始バイトまでのバイト数

    buf_reader.read(&mut byte_array).unwrap();

    offset_vec.push(u32::from_be_bytes(byte_array) as u64);

  }


  //TTC Header Version 2.0の場合は、numFontsの後にデジタル署名のTTC Headerが有るが、無視


  let max_index = num_fonts - 1;

  let font_name = Regex::new(r"^.+[/\\]|.[tT][tT][cC]$").unwrap().replace_all(ttc, "").to_string();

  let mut ttf_vec: Vec<String> = Vec::new();

  //numFontsの数だけ、くり返し

  for index in 0..num_fonts {

    let mut byte_vec: Vec<u8>;

    let current_index = index as usize;

    buf_reader.seek(SeekFrom::Start(offset_vec[current_index])).unwrap();

    if index == max_index {

      byte_vec = Vec::new();

      buf_reader.read_to_end(&mut byte_vec).unwrap();

    } else {

      let next_index = (index + 1) as usize;

      let size = (offset_vec[next_index] - offset_vec[current_index]) as usize;

      byte_vec = vec![0; size];

      buf_reader.read(&mut byte_vec).unwrap();

    }

    let ttf = format!("{}{}{}.ttf", out_dir, font_name, (index + 1));

    let mut buf_writer = BufWriter::new(File::create(&ttf).unwrap());

    buf_writer.write(&byte_vec).unwrap();

    buf_writer.flush().unwrap();


    ttf_vec.push(ttf);

  }

  return ttf_vec;

}



struct AppImpl {

  ttc: String,

  out_dir: String,

  message: String,

}


impl App for AppImpl {


  //ウィンドウのタイトルなどに利用されます。

  fn name(&self) -> &str {

    return ".ttcファイルを1つ以上の.ttfファイルに変換";

  }


  //描画の前に1回だけ起動されます。

  fn setup(&mut self, ctx: &CtxRef, _frame: &Frame, _storage: Option<&dyn Storage>) {


    //日本語のフォントを設定

    //.ttfか.otf

    let mut font_definitions = FontDefinitions::default();

    font_definitions.font_data.insert(

      "ja".to_owned(),

      FontData::from_static(include_bytes!("/usr/share/fonts/truetype/takao-gothic/TakaoPGothic.ttf"))

    );

    font_definitions.fonts_for_family.get_mut(&FontFamily::Proportional).unwrap().insert(0, "ja".to_owned());

    font_definitions.fonts_for_family.get_mut(&FontFamily::Monospace).unwrap().insert(0, "ja".to_owned());

    ctx.set_fonts(font_definitions);


    //ウィンドウのサイズを設定

    _frame.set_window_size(Vec2::new(800.0, 500.0));

  }


  //描画のたびに起動されます。

  fn update(&mut self, ctx: &CtxRef, _frame: &Frame) {


    //拡大

    ctx.set_pixels_per_point(1.5);


    CentralPanel::default().show(ctx, |ui| {


      ui.horizontal(|ui| {

        ui.label(".ttcファイル");

        ui.text_edit_singleline(&mut self.ttc);

      });


      ui.horizontal(|ui| {

        ui.label("書き込みディレクトリ");

        ui.text_edit_singleline(&mut self.out_dir);

      });


      if ui.button(".ttcファイルを1つ以上の.ttfファイルに変換").clicked() {


        let ttc = self.ttc.clone();

        let out_dir = self.out_dir.clone();


        self.message = String::from("");


        if ttc.is_empty() {

          self.message = String::from(".ttcファイルを記入してください。");

          return;

        }

        if out_dir.is_empty() {

          self.message = String::from("書き込みディレクトリを記入してください。");

          return;

        }


        if PathBuf::from(&ttc).is_file() == false {

          self.message = String::from(".ttcファイルに存在するファイルを記入してください。");

          return;

        }

        if PathBuf::from(&out_dir).is_dir() == false {

          self.message = String::from("書き込みディレクトリに存在するディレクトリを記入してください。");

          return;

        }


        ttc_to_ttf(&ttc, &out_dir);


        self.message = String::from(".ttcファイルから1つ以上の.ttfファイルへの変換が完了しました。");

      }


      ui.label(&self.message);

    });

  }

}


fn main() {

  eframe::run_native(

    Box::new(

      AppImpl{

        ttc: dirs::desktop_dir().unwrap().to_str().unwrap().to_string() + "/",

        out_dir: dirs::desktop_dir().unwrap().to_str().unwrap().to_string() + "/",

        message: String::from(""),

      }

    ),

    NativeOptions::default()

  );

}

――――――――――――――――――――

 コピペする場合は、2文字の全角空白を4文字の半角空白に、/usr/share/fonts/truetype/takao-gothic/TakaoPGothic.ttfを適切な日本語のフォントのファイルのパスに、置換してください。

  • Xで共有
  • Facebookで共有
  • はてなブックマークでブックマーク

作者を応援しよう!

ハートをクリックで、簡単に応援の気持ちを伝えられます。(ログインが必要です)

応援したユーザー

応援すると応援コメントも書けます

.ttcファイルを1つ以上の.ttfファイルへ変換しよう! エリファス1810 @Eliphas1810

★で称える

この小説が面白かったら★をつけてください。おすすめレビューも書けます。

カクヨムを、もっと楽しもう

この小説のおすすめレビューを見る

この小説のタグ