defmodule Pdf.Font.Metrics do
@moduledoc false
defmodule Glyph do
@moduledoc false
defstruct name: nil,
char_code: 0x00,
width: 0,
bbox: []
end
defstruct name: nil,
full_name: nil,
family_name: nil,
weight: nil,
italic_angle: nil,
encoding: nil,
first_char: 0,
last_char: 255,
ascender: nil,
descender: nil,
cap_height: nil,
x_height: nil,
fixed_pitch: false,
bbox: nil,
widths: [],
glyphs: %{},
kern_pairs: []
def widths(metrics, encoding \\ Pdf.Encoding.WinAnsi) do
Enum.map(encoding.characters(), fn {_, _, name} ->
case metrics.glyphs[name] do
nil -> 0
%{width: width} -> width
end
end)
end
def map_widths(font) do
Pdf.Encoding.WinAnsi.characters()
|> Enum.map(fn {_, char, name} ->
width =
case font.glyphs[name] do
nil ->
0
%{width: width} ->
width
end
{char, width}
end)
|> Map.new()
end
def process_line(<<"FontName ", data::binary>>, metrics), do: %{metrics | name: data}
def process_line(<<"FullName ", data::binary>>, metrics), do: %{metrics | full_name: data}
def process_line(<<"FamilyName ", data::binary>>, metrics), do: %{metrics | family_name: data}
def process_line(<<"Weight ", data::binary>>, metrics) do
%{metrics | weight: String.to_atom(String.downcase(data))}
end
def process_line(<<"ItalicAngle ", data::binary>>, metrics) do
case Float.parse(data) do
{angle, ""} -> %{metrics | italic_angle: angle}
_ -> metrics
end
end
def process_line(<<"EncodingScheme ", data::binary>>, metrics), do: %{metrics | encoding: data}
def process_line(<<"CapHeight ", data::binary>>, metrics),
do: %{metrics | cap_height: String.to_integer(data)}
def process_line(<<"XHeight ", data::binary>>, metrics),
do: %{metrics | x_height: String.to_integer(data)}
def process_line(<<"Ascender ", data::binary>>, metrics),
do: %{metrics | ascender: String.to_integer(data)}
def process_line(<<"Descender ", data::binary>>, metrics),
do: %{metrics | descender: String.to_integer(data)}
def process_line("IsFixedPitch true", metrics),
do: %{metrics | fixed_pitch: true}
def process_line(<<"FontBBox ", data::binary>>, metrics) do
bbox =
data
|> String.split(" ", trim: true)
|> Enum.map(fn f -> Float.parse(f) |> elem(0) end)
|> List.to_tuple()
%{metrics | bbox: bbox}
end
def process_line(<<"C ", _rest::binary>> = line, %{glyphs: glyphs} = metrics) do
glyph = parse_glyph(line)
if Pdf.Encoding.WinAnsi.from_name(glyph.name) do
%{metrics | glyphs: Map.put(glyphs, glyph.name, glyph)}
else
metrics
end
end
def process_line(<<"KPX ", data::binary>>, %{kern_pairs: kern_pairs} = metrics) do
case String.split(data) do
[first, second, amount] ->
first_char_code = Pdf.Encoding.WinAnsi.from_name(first)
second_char_code = Pdf.Encoding.WinAnsi.from_name(second)
if first_char_code && second_char_code do
{amount, _} = Integer.parse(amount)
%{metrics | kern_pairs: [{first_char_code, second_char_code, amount} | kern_pairs]}
else
metrics
end
_ ->
metrics
end
end
def process_line(_line, metrics), do: metrics
defp parse_glyph(line) do
line
|> String.trim()
|> String.split(~r/\s*;\s*/)
|> parse_glyph(%Glyph{})
end
defp parse_glyph([], glyph), do: glyph
defp parse_glyph(["" | tail], glyph),
do: parse_glyph(tail, glyph)
defp parse_glyph([<<"C ", _rest::binary>> | tail], glyph),
do: parse_glyph(tail, glyph)
defp parse_glyph([<<"N ", name::binary>> | tail], glyph) do
char_code = Pdf.Encoding.WinAnsi.from_name(name)
parse_glyph(tail, %{glyph | name: name, char_code: char_code})
end
defp parse_glyph([<<"WX ", width::binary>> | tail], glyph) do
glyph =
case Integer.parse(width) do
{width, _} -> %{glyph | width: width}
:error -> glyph
end
parse_glyph(tail, glyph)
end
defp parse_glyph([<<"B ", data::binary>> | tail], glyph) do
bbox =
data
|> String.split(" ", trim: true)
|> Enum.map(fn f -> Float.parse(f) |> elem(0) end)
|> Enum.map(fn f -> :erlang.float_to_binary(f, decimals: 1) end)
%{glyph | bbox: bbox}
parse_glyph(tail, glyph)
end
defp parse_glyph([_ | tail], glyph),
do: parse_glyph(tail, glyph)
end