///|
priv struct ParsedFontFace {
face : FontFace
font : @font.TTFont
}
///|
priv struct ParsedFontSet {
faces : Array[ParsedFontFace]
fallbacks : ReadOnlyArray[String]
}
///|
fn load_font_set(
fonts : FontSet,
limits : RasterLimits,
) -> ParsedFontSet raise RasterFailure {
guard fonts.faces.length() > 0 && fonts.faces.length() <= 128 else {
raise raster_fail(
"raster/font-limit", "Supply from 1 to 128 explicit font faces",
)
}
let parsed : Array[ParsedFontFace] = []
let cache : Array[(Bytes, @font.TTFont)] = []
let mut units = 0L
for face in fonts.faces {
guard face.family != "" && face.weight >= 1 && face.weight <= 1000 else {
raise raster_fail(
"raster/font-options", "Invalid explicit font family or weight",
)
}
let mut found : @font.TTFont? = None
for (data, font) in cache {
if data == face.data {
found = Some(font)
break
}
}
let font = match found {
Some(font) => font
None => {
units += face.data.length().to_int64()
guard units <= limits.max_source_units.to_int64() else {
raise raster_fail(
"raster/font-limit", "Total unique font bytes exceed the source budget",
)
}
let safe = validated_font_bytes(face.data, limits)
guard @font.parse_font(safe) is Some(font) else {
raise raster_fail(
"raster/font-data", "TrueType outline parser rejected the validated font",
)
}
cache.push((face.data, font))
font
}
}
parsed.push({ face, font, })
}
{ faces: parsed, fallbacks: fonts.fallback_families, }
}
///|
fn font_for_text(
fonts : ParsedFontSet,
family : String,
weight : String,
style : String,
source_id : String,
) -> ParsedFontFace raise RasterFailure {
let weight = match weight {
"normal" => 400
"bold" => 700
_ =>
@string.parse_int(weight) catch {
_ =>
raise raster_fail(
"raster/font-style",
"Unsupported font weight",
source_id~,
)
}
}
let italic = match style {
"normal" => false
"italic" | "oblique" => true
_ =>
raise raster_fail(
"raster/font-style",
"Unsupported font style",
source_id~,
)
}
for part in family.split(",") {
let name = part
.trim()
.to_owned()
.replace(old="'", new="")
.replace(old="\"", new="")
for parsed in fonts.faces {
if parsed.face.family == name &&
parsed.face.weight == weight &&
parsed.face.italic == italic {
return parsed
}
}
}
raise raster_fail(
"raster/font-style",
"No supplied face matches the required family, weight and style",
source_id~,
)
}
///|
fn glyph_face(
fonts : ParsedFontSet,
preferred : ParsedFontFace,
codepoint : Int32,
source_id : String,
) -> ParsedFontFace raise RasterFailure {
if preferred.font.glyph_index(codepoint) != 0 {
return preferred
}
for family in fonts.fallbacks {
for face in fonts.faces {
if face.face.family == family && face.font.glyph_index(codepoint) != 0 {
return face
}
}
}
raise raster_fail(
"raster/missing-glyph",
"No supplied font contains required Unicode scalar " + codepoint.to_string(),
source_id~,
)
}
///|
pub fn FontSet::subset_for_svg(
self : FontSet,
source : String,
limits~ : RasterLimits = RasterLimits::default(),
) -> FontSubsetResult {
try {
validate_raster_limits(limits)
guard source.length() <= limits.max_source_units else {
raise raster_fail(
"raster/source-limit", "Generated SVG exceeds the source budget",
)
}
let nodes = parse_svg_xml(source, max_nodes=limits.max_svg_nodes)
let parsed = load_font_set(self, limits)
ignore(normalize_generated_svg(source, "light", parsed, limits))
let cps : Map[Int32, Bool] = Map([])
for node in nodes {
if node.raw_name == "text" || node.raw_name == "tspan" {
for child in node.children {
let content = nodes[child]
if content.kind == Text && content.value.trim() != "" {
// Portable scalar iteration includes surrogate pairs.
for scalar in content.value.iter() {
cps[scalar.to_int()] = true
guard cps.length() <= limits.max_glyphs else {
raise raster_fail(
"raster/glyph-limit", "Font subset codepoint budget exceeded",
)
}
}
}
}
}
}
let coverage = cps.keys().to_array()
coverage.sort()
let faces : Array[FontFace] = []
let cache : Array[(Bytes, Bytes)] = []
for face in self.faces {
let mut subset : Bytes? = None
for (original, data) in cache {
if original == face.data {
subset = Some(data)
break
}
}
let data = match subset {
Some(data) => data
None => {
let safe = validated_font_bytes(face.data, limits)
guard @font.subset_font(safe, coverage) is Some(data) else {
raise raster_fail(
"raster/font-subset", "Unable to subset the supplied static TrueType font",
)
}
cache.push((face.data, data))
data
}
}
faces.push({ ..face, data, })
}
{
fonts: Some(FontSet::new(faces, fallback_families=self.fallback_families)),
diagnostics: [],
}
} catch {
error => { fonts: None, diagnostics: [raster_diagnostic(error)], }
}
}