| 69328 |      1 | /*  Title:      Pure/Tools/fontforge.scala
 | 
|  |      2 |     Author:     Makarius
 | 
|  |      3 | 
 | 
| 69333 |      4 | Support for fontforge and its scripting language: see
 | 
|  |      5 | /usr/share/doc/fontforge/scripting.html e.g. on Ubuntu Linux installation.
 | 
| 69328 |      6 | */
 | 
|  |      7 | 
 | 
|  |      8 | package isabelle
 | 
|  |      9 | 
 | 
|  |     10 | 
 | 
|  |     11 | import java.io.{File => JFile}
 | 
|  |     12 | import java.util.Locale
 | 
|  |     13 | 
 | 
|  |     14 | 
 | 
| 75393 |     15 | object Fontforge {
 | 
| 69328 |     16 |   /** scripting language **/
 | 
|  |     17 | 
 | 
|  |     18 |   type Script = String
 | 
|  |     19 | 
 | 
|  |     20 | 
 | 
|  |     21 |   /* concrete syntax */
 | 
|  |     22 | 
 | 
| 75393 |     23 |   def string(s: String): Script = {
 | 
| 69329 |     24 |     def err(c: Char): Nothing =
 | 
| 71163 |     25 |       error("Bad character \\u" + String.format(Locale.ROOT, "%04x", Integer.valueOf(c)) +
 | 
| 69329 |     26 |         " in fontforge string " + quote(s))
 | 
| 69328 |     27 | 
 | 
| 69329 |     28 |     val q = if (s.contains('"')) '\'' else '"'
 | 
| 69328 |     29 | 
 | 
| 75393 |     30 |     def escape(c: Char): String = {
 | 
| 69329 |     31 |       if (c == '\u0000' || c == '\r' || c == q) err(c)
 | 
| 69328 |     32 |       else if (c == '\n') "\\n"
 | 
|  |     33 |       else if (c == '\\') "\\\\"
 | 
|  |     34 |       else c.toString
 | 
|  |     35 |     }
 | 
|  |     36 | 
 | 
|  |     37 |     if (s.nonEmpty && s(0) == '\\') err('\\')
 | 
| 71601 |     38 |     s.iterator.map(escape).mkString(q.toString, "", q.toString)
 | 
| 69328 |     39 |   }
 | 
|  |     40 | 
 | 
| 69333 |     41 |   def file_name(path: Path): Script = string(File.standard_path(path))
 | 
| 69328 |     42 | 
 | 
| 69333 |     43 | 
 | 
|  |     44 |   /* commands */
 | 
|  |     45 | 
 | 
| 69335 |     46 |   def command_list(cmds: List[String]): Script = cat_lines(cmds)
 | 
|  |     47 |   def commands(cmds: String*): Script = command_list(cmds.toList)
 | 
|  |     48 | 
 | 
| 69333 |     49 |   // fonts
 | 
|  |     50 |   def open(path: Path): Script = "Open(" + file_name(path) +")"
 | 
| 69335 |     51 |   def close: Script = "Close()"
 | 
| 69333 |     52 |   def generate(path: Path): Script = "Generate(" + file_name(path) +")"
 | 
|  |     53 | 
 | 
|  |     54 |   // selection
 | 
|  |     55 |   def select_all: Script = "SelectAll()"
 | 
|  |     56 |   def select_none: Script = "SelectNone()"
 | 
|  |     57 |   def select_invert: Script = "SelectInvert()"
 | 
| 69335 |     58 |   def select(args: Seq[Int]): Script =
 | 
|  |     59 |     command_list(select_none :: args.toList.map(code => "SelectMoreSingletons(" + code + ")"))
 | 
|  |     60 | 
 | 
| 69333 |     61 |   def clear: Script = "Clear()"
 | 
|  |     62 |   def copy: Script = "Copy()"
 | 
|  |     63 |   def paste: Script = "Paste()"
 | 
|  |     64 | 
 | 
|  |     65 | 
 | 
|  |     66 | 
 | 
|  |     67 |   /** execute fontforge program **/
 | 
| 69328 |     68 | 
 | 
|  |     69 |   def execute(script: Script, args: String = "", cwd: JFile = null): Process_Result =
 | 
| 75394 |     70 |     Isabelle_System.with_tmp_file("fontforge") { script_file =>
 | 
| 69328 |     71 |       File.write(script_file, script)
 | 
|  |     72 |       Isabelle_System.bash(File.bash_path(Path.explode("$ISABELLE_FONTFORGE")) +
 | 
|  |     73 |         " -lang=ff -script " + File.bash_path(script_file) + " " + args)
 | 
| 75394 |     74 |     }
 | 
| 69333 |     75 | 
 | 
| 75393 |     76 |   def font_domain(path: Path, strict: Boolean = false): List[Int] = {
 | 
| 69335 |     77 |     val script =
 | 
|  |     78 |       commands(
 | 
|  |     79 |         open(path),
 | 
|  |     80 |         "i = 0",
 | 
| 69370 |     81 |         "while (i < CharCnt() && i < 0x110000)",
 | 
| 69335 |     82 |         "  if (" + (if (strict) "DrawsSomething" else "WorthOutputting") + "(i)); Print(i); endif",
 | 
|  |     83 |         "  i = i + 1",
 | 
|  |     84 |         "endloop",
 | 
|  |     85 |         close)
 | 
|  |     86 |     Library.trim_split_lines(execute(script).check.out).map(Value.Int.parse)
 | 
|  |     87 |   }
 | 
|  |     88 | 
 | 
|  |     89 | 
 | 
|  |     90 |   /* font names */
 | 
|  |     91 | 
 | 
|  |     92 |   sealed case class Font_Names(
 | 
|  |     93 |     fontname: String = "",
 | 
|  |     94 |     familyname: String = "",
 | 
|  |     95 |     fullname: String = "",
 | 
|  |     96 |     weight: String = "",
 | 
|  |     97 |     copyright: String = "",
 | 
| 75393 |     98 |     fontversion: String = ""
 | 
|  |     99 |   ) {
 | 
| 69335 |    100 |     override def toString: String = fontname
 | 
|  |    101 | 
 | 
|  |    102 |     def ttf: Path = Path.explode(fontname).ext("ttf")
 | 
|  |    103 | 
 | 
| 75393 |    104 |     def update(prefix: String = "", version: String = ""): Font_Names = {
 | 
| 69335 |    105 |       val fontversion1 = proper_string(version) getOrElse fontversion
 | 
|  |    106 |       if (prefix == "") copy(fontversion = fontversion1)
 | 
|  |    107 |       else {
 | 
|  |    108 |         copy(fontname = prefix + fontname,
 | 
|  |    109 |           familyname = prefix + " " + familyname,
 | 
|  |    110 |           fullname = prefix + " " + fullname,
 | 
|  |    111 |           weight = weight,
 | 
|  |    112 |           copyright = copyright,
 | 
|  |    113 |           fontversion = fontversion1)
 | 
|  |    114 |       }
 | 
|  |    115 |     }
 | 
|  |    116 | 
 | 
|  |    117 |     def set: Script =
 | 
| 71601 |    118 |       List(fontname, familyname, fullname, weight, copyright, fontversion).map(string).
 | 
| 69335 |    119 |         mkString("SetFontNames(", ",", ")")
 | 
|  |    120 |   }
 | 
|  |    121 | 
 | 
| 75393 |    122 |   def font_names(path: Path): Font_Names = {
 | 
| 69335 |    123 |     val script =
 | 
|  |    124 |       commands(
 | 
|  |    125 |         open(path),
 | 
|  |    126 |         "Print($fontname)",
 | 
|  |    127 |         "Print($familyname)",
 | 
|  |    128 |         "Print($fullname)",
 | 
|  |    129 |         "Print($weight)",
 | 
|  |    130 |         "Print($fontversion)",
 | 
|  |    131 |         "Print($copyright)",
 | 
|  |    132 |         close)
 | 
|  |    133 |     Library.trim_split_lines(execute(script).check.out) match {
 | 
|  |    134 |       case fontname :: familyname :: fullname :: weight :: fontversion :: copyright =>
 | 
|  |    135 |         Font_Names(fontname, familyname, fullname, weight, cat_lines(copyright), fontversion)
 | 
|  |    136 |       case res => error(cat_lines("Bad font names: " :: res))
 | 
|  |    137 |     }
 | 
| 69333 |    138 |   }
 | 
| 69328 |    139 | }
 |