defmodule Pdf.ExternalFont do @moduledoc false @derive {Inspect, only: [:name, :family_name, :weight, :italic_angle]} defstruct name: nil, font_file: nil, dictionary: 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: 0, x_height: nil, fixed_pitch: false, bbox: nil, widths: [], glyph_widths: %{}, glyphs: %{}, kern_pairs: [] import Pdf.Utils alias Pdf.Font.Metrics alias Pdf.{Array, Dictionary} @stream_start "\nstream\n" @stream_end "\nendstream" def load(path) do font_metrics = path |> File.stream!() |> Enum.reduce(%Metrics{}, fn line, metrics -> Metrics.process_line(String.replace_suffix(line, "\n", ""), metrics) end) font_file_name = Path.rootname(path) <> ".pfb" font_file = File.read!(font_file_name) part1 = (:binary.match(font_file, "eexec") |> elem(0)) + 6 part2 = (:binary.match(font_file, "00000000") |> elem(0)) - part1 part3 = byte_size(font_file) - part1 - part2 widths = Metrics.widths(font_metrics) last_char = font_metrics.first_char + length(widths) - 1 %__MODULE__{ name: font_metrics.name, full_name: font_metrics.full_name, family_name: font_metrics.family_name, weight: font_metrics.weight, italic_angle: font_metrics.italic_angle, encoding: font_metrics.encoding, first_char: font_metrics.first_char, last_char: last_char, ascender: font_metrics.ascender, descender: font_metrics.descender, cap_height: font_metrics.cap_height || 0, x_height: font_metrics.x_height, fixed_pitch: font_metrics.fixed_pitch, bbox: font_metrics.bbox, widths: widths, glyph_widths: Metrics.map_widths(font_metrics), glyphs: font_metrics.glyphs, kern_pairs: font_metrics.kern_pairs, font_file: font_file, dictionary: font_file_dictionary(part1, part2, part3) } end def font_dictionary(font, id, desc_id) do Dictionary.new() |> Dictionary.put("Type", n("Font")) |> Dictionary.put("Subtype", n("Type1")) |> Dictionary.put("Name", n("F#{id}")) |> Dictionary.put("BaseFont", n(font.name)) |> Dictionary.put("FirstChar", font.first_char) |> Dictionary.put("LastChar", font.last_char) |> Dictionary.put("Widths", Array.new(Enum.map(font.widths, &to_string/1))) |> Dictionary.put("Encoding", n("WinAnsiEncoding")) |> Dictionary.put("FontDescriptor", desc_id) end def font_descriptor_dictionary(font, ff_id) do flags = 32 Dictionary.new() |> Dictionary.put("Type", n("FontDescriptor")) |> Dictionary.put("Flags", flags) |> Dictionary.put("FontName", n(font.name)) |> Dictionary.put("StemV", 100) |> Dictionary.put("Ascent", font.ascender / 1) |> Dictionary.put("Descent", font.descender / 1) |> Dictionary.put("FontBBox", Array.new(Tuple.to_list(font.bbox))) |> Dictionary.put("ItalicAngle", font.italic_angle) |> Dictionary.put("CapHeight", font.cap_height / 1) |> Dictionary.put("FontFile", ff_id) end def font_file_dictionary(part1, part2, part3) do Dictionary.new() |> Dictionary.put("Length1", part1) |> Dictionary.put("Length2", part2) |> Dictionary.put("Length3", part3) |> Dictionary.put("Length", part1 + part2 + part3) end def size(%__MODULE__{} = font) do # size_of(font.dictionary) + byte_size(font.font_file) + byte_size(@stream_start <> @stream_end) byte_size(to_iolist(font) |> Enum.join()) end @doc """ Returns the width of the specific character Examples: iex> ExternalFont.width(font, "A") 123 """ def width(font, <> = str) when is_binary(str) do width(font, char_code) end def width(font, char_code) do font.glyph_widths[char_code] || 0 end def kern_text(_font, ""), do: [""] def kern_text(font, <>) do font.kern_pairs |> Enum.find(fn {f, s, _amount} -> f == first && s == second end) |> case do {f, _s, amount} -> [<>, -amount | kern_text(font, <>)] nil -> [head | tail] = kern_text(font, <>) [<> | tail] end end def kern_text(_font, <<_::integer>> = char), do: [char] def to_iolist(%__MODULE__{} = font) do Pdf.Export.to_iolist([ font.dictionary, @stream_start, font.font_file, @stream_end ]) end defimpl Pdf.Size do def size_of(%Pdf.ExternalFont{} = font), do: Pdf.ExternalFont.size(font) end defimpl Pdf.Export do def to_iolist(%Pdf.ExternalFont{} = font), do: Pdf.ExternalFont.to_iolist(font) end end