Skip to content

Commit 916507b

Browse files
committed
Resolve physical font names from logical names
1 parent 91e771b commit 916507b

4 files changed

Lines changed: 66 additions & 18 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ website/static/api
1313
_gen/
1414
.hugo_build.lock
1515
.claude/settings.local.json
16+
.vscode/mcp.json

‎awt/src/main/scala/org/nspl/awtutil.scala‎

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ trait JavaAWTUtil {
120120
build: Build[K],
121121
os: java.io.OutputStream,
122122
width: Int = 500,
123-
format: String = "pdf"
123+
format: String = "pdf",
124+
textAsShapes : Boolean = true
124125
)(implicit
125126
er: Renderer[K, JavaRC]
126127
) = {
@@ -135,7 +136,7 @@ trait JavaAWTUtil {
135136
val aspect = elem.bounds.h / elem.bounds.w
136137
val height = (width * aspect).toInt
137138
val g2d = new VectorGraphics2D()
138-
val renderingContext = new JavaRC(g2d, doRender = true, textAsShapes = true)
139+
val renderingContext = new JavaRC(g2d, doRender = true, textAsShapes = textAsShapes)
139140

140141
val processor =
141142
format match {
@@ -156,13 +157,14 @@ trait JavaAWTUtil {
156157
elem: Build[K],
157158
os: java.io.OutputStream,
158159
width: Int = 1000,
159-
mimeType: String = "image/png"
160+
mimeType: String = "image/png",
161+
textAsShapes : Boolean = true
160162
)(implicit
161163
er: Renderer[K, JavaRC]
162164
) = {
163165
mimeType.split("/").last match {
164166
case "pdf" | "svg" | "eps" | "svg+xml" =>
165-
writeVector(elem, os, width, mimeType.split("/").last)
167+
writeVector(elem, os, width, mimeType.split("/").last, textAsShapes)
166168
case _ => writeBitmap(elem, os, width, mimeType)
167169
}
168170
}
@@ -308,13 +310,14 @@ trait JavaAWTUtil {
308310
f: File,
309311
elem: Build[K],
310312
width: Int,
311-
mimeType: String
313+
mimeType: String,
314+
textAsShapes : Boolean
312315
)(implicit
313316
er: Renderer[K, JavaRC]
314317
): File = {
315318
val os = new BufferedOutputStream(new FileOutputStream(f))
316319
try {
317-
write(elem, os, width, mimeType)
320+
write(elem, os, width, mimeType, textAsShapes)
318321
} finally {
319322
os.close
320323
}
@@ -324,39 +327,43 @@ trait JavaAWTUtil {
324327
def svgToFile[K <: Renderable[K]](
325328
f: File,
326329
elem: Build[K],
327-
width: Int
330+
width: Int,
331+
textAsShapes : Boolean
328332
)(implicit
329333
er: Renderer[K, JavaRC]
330334
): File = {
331-
renderToFile(f, elem, width, "svg")
335+
renderToFile(f, elem, width, "svg", textAsShapes : Boolean)
332336
f
333337
}
334338
def pdfToFile[K <: Renderable[K]](
335339
f: File,
336340
elem: Build[K],
337-
width: Int
341+
width: Int,
342+
textAsShapes : Boolean
338343
)(implicit
339344
er: Renderer[K, JavaRC]
340345
): File = {
341-
renderToFile(f, elem, width, "application/pdf")
346+
renderToFile(f, elem, width, "application/pdf", textAsShapes)
342347
f
343348
}
344349

345350
def pdfToFile[K <: Renderable[K]](
346351
f: File,
347-
elem: Build[K]
352+
elem: Build[K],
353+
textAsShapes : Boolean
348354
)(implicit
349355
er: Renderer[K, JavaRC]
350-
): File = pdfToFile(f, elem, 500)
356+
): File = pdfToFile(f, elem, 500, textAsShapes)
351357

352358
def pdfToFile[K <: Renderable[K]](
353359
elem: Build[K],
354-
width: Int = 500
360+
width: Int = 500,
361+
textAsShapes : Boolean = true
355362
)(implicit
356363
er: Renderer[K, JavaRC]
357364
) = {
358365
val f = java.io.File.createTempFile("nspl", ".pdf")
359-
renderToFile(f, elem, width, "application/pdf")
366+
renderToFile(f, elem, width, "application/pdf", textAsShapes)
360367
f
361368
}
362369

@@ -367,7 +374,7 @@ trait JavaAWTUtil {
367374
)(implicit
368375
er: Renderer[K, JavaRC]
369376
): File = {
370-
renderToFile(f, elem, width, "image/png")
377+
renderToFile(f, elem, width, "image/png", false)
371378
f
372379
}
373380

‎saddle/src/test/scala/plots/plots.test.scala‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ class SaddlePlotSpec extends munit.FunSuite {
455455

456456
// show(gallery)
457457
println(pngToFile(gallery.build, width = 10000))
458-
println(pdfToFile(gallery.build))
458+
println(pdfToFile(gallery.build, textAsShapes = false))
459459
println(pdfToFile(text))
460460
while (false) {
461461
gallery.build

‎shared-jvm/src/main/scala/org/nspl/font.scala‎

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,48 @@ package org.nspl
22
import java.awt.{Font => JFont}
33

44
private[nspl] object JavaFontConversion {
5-
def font2font(myFont: Font): JFont = new JFont(myFont.name, JFont.PLAIN, myFont.size)
6-
5+
6+
/** Logical font names that Java maps to composite fonts.
7+
* VectorGraphics2D hangs on macOS when trying to resolve these
8+
* to physical fonts during PDF/SVG/EPS generation.
9+
*/
10+
private val logicalFontNames = Set(
11+
"sansserif", "serif", "monospaced", "dialog", "dialoginput"
12+
)
13+
14+
/** Cache of logical font name -> resolved physical font family name */
15+
private lazy val physicalFontMap: Map[String, String] = {
16+
val ge = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment()
17+
val available = ge.getAvailableFontFamilyNames()
18+
logicalFontNames.flatMap { logical =>
19+
val jf = new JFont(logical, JFont.PLAIN, 12)
20+
val family = jf.getFamily()
21+
if (!logicalFontNames.contains(family.toLowerCase)) {
22+
Some(logical -> family)
23+
} else {
24+
val fallback = logical match {
25+
case "sansserif" | "dialog" | "dialoginput" =>
26+
available.find(f => f == "Helvetica" || f == "Arial" || f == "Liberation Sans")
27+
case "serif" =>
28+
available.find(f => f == "Times" || f == "Times New Roman" || f == "Liberation Serif")
29+
case "monospaced" =>
30+
available.find(f => f == "Courier" || f == "Courier New" || f == "Liberation Mono")
31+
case _ => None
32+
}
33+
fallback.map(logical -> _)
34+
}
35+
}.toMap
36+
}
37+
38+
private def resolvePhysicalName(name: String): String = {
39+
val lower = name.toLowerCase
40+
if (logicalFontNames.contains(lower))
41+
physicalFontMap.getOrElse(lower, name)
42+
else name
43+
}
44+
45+
def font2font(myFont: Font): JFont =
46+
new JFont(resolvePhysicalName(myFont.name), JFont.PLAIN, myFont.size)
747

848
}
949

0 commit comments

Comments
 (0)