Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
philippnurullin committed Aug 27, 2020
2 parents 308f6ac + 11bd018 commit 6e29725
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 297 deletions.
125 changes: 125 additions & 0 deletions scripts/GenerateNLVersion.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import org.w3c.dom.Attr
import org.w3c.dom.Document
import org.w3c.dom.Node
import org.w3c.dom.NodeList
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.TimeUnit
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory

/**
* The scripts generates no ligature version of JetBrains Mono called JetBrains Mono NL
*
* ttx command is required to run this script
*
* @author Konstantin Bulenkov
*/
@Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
fun main() {
File("./ttf/")
.listFiles { _, name -> name.endsWith(".ttf") && !name.startsWith("JetBrainsMonoNL") }
.forEach {
val ttx = it.nameWithoutExtension + ".ttx"
val dir = it.parentFile
File(dir, ttx).deleteAndLog()
val doc = ttf2Document(it)
File(dir, ttx).deleteAndLog()
if (doc != null) {
generateNoLigaturesFont(File(dir, it.name), doc)
}
}
}

fun ttf2Document(file: File): Document? {
"ttx ${file.name}".runCommand(file.parentFile)
val ttx = file.parentFile.listFiles { _, name -> name == "${file.nameWithoutExtension}.ttx" }?.first() ?: return null
val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
return documentBuilder.parse(ttx)
}

fun generateNoLigaturesFont(file: File, doc: Document) {
val nlName = file.nameWithoutExtension.replace("JetBrainsMono", "JetBrainsMonoNL")
val ttx = "$nlName.ttx"
val ttf = "$nlName.ttf"
val dir = File(file.parentFile, "No ligatures")
File(dir, ttf).deleteAndLog()
doc.removeLigas("/ttFont/GlyphOrder", "GlyphID")
doc.removeLigas("/ttFont/glyf", "TTGlyph")
doc.removeLigas("/ttFont/hmtx", "mtx")
doc.removeLigas("/ttFont/post/extraNames", "psName")
doc.removeLigas("/ttFont/GDEF/GlyphClassDef", "ClassDef", attName = "glyph")
doc.removeNode("/ttFont/GPOS")
doc.removeNode("/ttFont/GSUB")

val xPath = XPathFactory.newInstance().newXPath()
val nameRecords = (xPath.evaluate("/ttFont/name/namerecord", doc, XPathConstants.NODESET) as NodeList).asList()
nameRecords.forEach {
if (!it.textContent.contains("trademark")) {
it.textContent = it.textContent
.replace("JetBrains Mono", "JetBrains Mono NL")
.replace("JetBrainsMono", "JetBrainsMonoNL")
}
}

val ttxFile = File(dir, ttx)
doc.saveAs(ttxFile)
"ttx $ttx".runCommand(dir)
ttxFile.deleteAndLog()
}

class NodeListWrapper(val nodeList: NodeList) : AbstractList<Node>(), RandomAccess {
override val size: Int
get() = nodeList.length

override fun get(index: Int): Node = nodeList.item(index)
}

////////////////////// Utility functions and data classes //////////////////////

fun NodeList.asList(): List<Node> = NodeListWrapper(this)

fun String.runCommand(workingDir: File) {
ProcessBuilder(*split(" ").toTypedArray())
.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.start()
.waitFor(1, TimeUnit.MINUTES)
}

fun Document.saveAs(file: File) {
val transformer = TransformerFactory.newInstance().newTransformer()
transformer.transform(DOMSource(this), StreamResult(FileOutputStream(file)))
}

fun Document.removeLigas(parentPath: String, nodeName: String, attName:String = "name") {
val xPath = XPathFactory.newInstance().newXPath()
val parent = xPath.evaluate(parentPath, this, XPathConstants.NODE) as Node
val nodeFilter = "$parentPath/$nodeName[substring(@$attName, string-length(@$attName)-4) = '.liga']"
val nodes = (xPath.evaluate(nodeFilter, this, XPathConstants.NODESET) as NodeList).asList()
nodes.forEach { parent.removeChild(it) }
}

fun Document.removeNode(path: String) {
val xPath = XPathFactory.newInstance().newXPath()
val parent = xPath.evaluate(path.substringBeforeLast("/"), this, XPathConstants.NODE)
if (parent is Node) {
val child = xPath.evaluate(path, this, XPathConstants.NODE)
if (child is Node) {
parent.removeChild(child)
}
}
}

fun File.deleteAndLog() {
if (!exists()) return
println("Deleting $absolutePath")
val result = delete()
println("[$result]".toUpperCase())
if (!result) deleteOnExit()
}
Loading

0 comments on commit 6e29725

Please sign in to comment.