1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 04:57:44 +00:00

LibPDF: Add new Type1FontProgram base class

We are planning to add support for CFF fonts to read Type1 fonts, and
therefore much of the logic already found in PS1FontProgram will be
useful for representing the Type1 fonts read from CFF.

This commit moves the PS1-independent bits of PS1FontProgram into a new
Type1FontProgram base class that can be used as the base for CFF-based
Type1 fonts in the future. The Type1Font class uses this new type now
instead of storing a PS1FontProgram pointer. While doing this
refactoring I also took care of making some minor adjustments to the
PS1FontProgram API, namely:

 * Its create() method is static and returns a
   NonnullRefPtr<Type1FontProgram>.
 * Many (all?) of the parse_* methods are now static.
 * Added const where possible.

Notably, the Type1FontProgram also contains at the moment the code that
parses the CharString data from the PS1 program. This logic is very
similar in CFF files, so after some minor adjustments later on it should
be possible to reuse most of it.
This commit is contained in:
Rodrigo Tobar 2023-01-07 12:21:25 +08:00 committed by Andreas Kling
parent e751ec2089
commit 416585f75a
7 changed files with 535 additions and 476 deletions

View file

@ -14,38 +14,7 @@
namespace PDF {
enum Command {
HStem = 1,
VStem = 3,
VMoveTo,
RLineTo,
HLineTo,
VLineTo,
RRCurveTo,
ClosePath,
CallSubr,
Return,
Extended,
HSbW,
EndChar,
RMoveTo = 21,
HMoveTo,
VHCurveTo = 30,
HVCurveTo
};
enum ExtendedCommand {
DotSection,
VStem3,
HStem3,
Seac = 6,
Div = 12,
CallOtherSubr = 16,
Pop,
SetCurrentPoint = 33,
};
PDFErrorOr<void> PS1FontProgram::create(ReadonlyBytes const& bytes, RefPtr<Encoding> encoding, size_t cleartext_length, size_t encrypted_length)
PDFErrorOr<NonnullRefPtr<Type1FontProgram>> PS1FontProgram::create(ReadonlyBytes const& bytes, RefPtr<Encoding> encoding, size_t cleartext_length, size_t encrypted_length)
{
Reader reader(bytes);
if (reader.remaining() == 0)
@ -58,13 +27,14 @@ PDFErrorOr<void> PS1FontProgram::create(ReadonlyBytes const& bytes, RefPtr<Encod
if (!seek_name(reader, CommonNames::Encoding))
return error("Missing encoding array");
auto font_program = adopt_ref(*new PS1FontProgram());
if (encoding) {
// 9.6.6.2 Encodings for Type 1 Fonts:
// An Encoding entry may override a Type 1 fonts mapping from character codes to character names.
m_encoding = encoding;
font_program->set_encoding(move(encoding));
} else {
if (TRY(parse_word(reader)) == "StandardEncoding") {
m_encoding = Encoding::standard_encoding();
font_program->set_encoding(Encoding::standard_encoding());
} else {
HashMap<u16, CharDescriptor> descriptors;
@ -78,385 +48,21 @@ PDFErrorOr<void> PS1FontProgram::create(ReadonlyBytes const& bytes, RefPtr<Encod
descriptors.set(char_code, { name.starts_with('/') ? name.substring_view(1) : name.view(), char_code });
}
}
m_encoding = TRY(Encoding::create(descriptors));
font_program->set_encoding(TRY(Encoding::create(descriptors)));
}
}
bool found_font_matrix = seek_name(reader, "FontMatrix");
if (found_font_matrix) {
auto array = TRY(parse_number_array(reader, 6));
m_font_matrix = { array[0], array[1], array[2], array[3], array[4], array[5] };
font_program->set_font_matrix({ array[0], array[1], array[2], array[3], array[4], array[5] });
} else {
m_font_matrix = { 0.001f, 0.0f, 0.0f, 0.001f, 0.0f, 0.0f };
font_program->set_font_matrix({ 0.001f, 0.0f, 0.0f, 0.001f, 0.0f, 0.0f });
}
auto decrypted = TRY(decrypt(reader.bytes().slice(cleartext_length, encrypted_length), 55665, 4));
return parse_encrypted_portion(decrypted);
}
RefPtr<Gfx::Bitmap> PS1FontProgram::rasterize_glyph(u32 char_code, float width, Gfx::GlyphSubpixelOffset subpixel_offset)
{
auto path = build_char(char_code, width, subpixel_offset);
auto bounding_box = path.bounding_box().size();
u32 w = (u32)ceilf(bounding_box.width()) + 2;
u32 h = (u32)ceilf(bounding_box.height()) + 2;
Gfx::PathRasterizer rasterizer(Gfx::IntSize(w, h));
rasterizer.draw_path(path);
return rasterizer.accumulate();
}
Gfx::Path PS1FontProgram::build_char(u32 char_code, float width, Gfx::GlyphSubpixelOffset subpixel_offset)
{
auto maybe_glyph = m_glyph_map.get(char_code);
if (!maybe_glyph.has_value())
return {};
auto& glyph = maybe_glyph.value();
auto transform = Gfx::AffineTransform()
.translate(subpixel_offset.to_float_point())
.multiply(glyph_transform_to_device_space(glyph, width));
// Translate such that the top-left point is at [0, 0].
auto bounding_box = glyph.path.bounding_box();
Gfx::FloatPoint translation(-bounding_box.x(), -(bounding_box.y() + bounding_box.height()));
transform.translate(translation);
return glyph.path.copy_transformed(transform);
}
Gfx::FloatPoint PS1FontProgram::glyph_translation(u32 char_code, float width) const
{
auto maybe_glyph = m_glyph_map.get(char_code);
if (!maybe_glyph.has_value())
return {};
auto& glyph = maybe_glyph.value();
auto transform = glyph_transform_to_device_space(glyph, width);
// Undo the translation we applied earlier.
auto bounding_box = glyph.path.bounding_box();
Gfx::FloatPoint translation(bounding_box.x(), bounding_box.y() + bounding_box.height());
return transform.map(translation);
}
Gfx::AffineTransform PS1FontProgram::glyph_transform_to_device_space(Glyph const& glyph, float width) const
{
auto scale = width / (m_font_matrix.a() * glyph.width + m_font_matrix.e());
auto transform = m_font_matrix;
// Convert character space to device space.
transform.scale(scale, -scale);
return transform;
}
PDFErrorOr<PS1FontProgram::Glyph> PS1FontProgram::parse_glyph(ReadonlyBytes const& data, GlyphParserState& state)
{
auto push = [&](float value) -> PDFErrorOr<void> {
if (state.sp >= state.stack.size())
return error("Operand stack overflow");
state.stack[state.sp++] = value;
return {};
};
auto pop = [&]() -> float {
return state.sp ? state.stack[--state.sp] : 0.0f;
};
auto& path = state.glyph.path;
// Parse the stream of parameters and commands that make up a glyph outline.
for (size_t i = 0; i < data.size(); ++i) {
auto require = [&](unsigned num) -> PDFErrorOr<void> {
if (i + num >= data.size())
return error("Malformed glyph outline definition");
return {};
};
int v = data[i];
if (v == 255) {
TRY(require(4));
int a = data[++i];
int b = data[++i];
int c = data[++i];
int d = data[++i];
TRY(push((a << 24) + (b << 16) + (c << 8) + d));
} else if (v >= 251) {
TRY(require(1));
auto w = data[++i];
TRY(push(-((v - 251) * 256) - w - 108));
} else if (v >= 247) {
TRY(require(1));
auto w = data[++i];
TRY(push(((v - 247) * 256) + w + 108));
} else if (v >= 32) {
TRY(push(v - 139));
} else {
// Not a parameter but a command byte.
switch (v) {
case HStem:
case VStem:
state.sp = 0;
break;
case VMoveTo: {
auto dy = pop();
state.point.translate_by(0.0f, dy);
if (state.flex_feature) {
state.flex_sequence[state.flex_index++] = state.point.x();
state.flex_sequence[state.flex_index++] = state.point.y();
} else {
path.move_to(state.point);
}
state.sp = 0;
break;
}
case RLineTo: {
auto dy = pop();
auto dx = pop();
state.point.translate_by(dx, dy);
path.line_to(state.point);
state.sp = 0;
break;
}
case HLineTo: {
auto dx = pop();
state.point.translate_by(dx, 0.0f);
path.line_to(state.point);
state.sp = 0;
break;
}
case VLineTo: {
auto dy = pop();
state.point.translate_by(0.0f, dy);
path.line_to(state.point);
state.sp = 0;
break;
}
case RRCurveTo: {
auto dy3 = pop();
auto dx3 = pop();
auto dy2 = pop();
auto dx2 = pop();
auto dy1 = pop();
auto dx1 = pop();
auto& point = state.point;
path.cubic_bezier_curve_to(
point + Gfx::FloatPoint(dx1, dy1),
point + Gfx::FloatPoint(dx1 + dx2, dy1 + dy2),
point + Gfx::FloatPoint(dx1 + dx2 + dx3, dy1 + dy2 + dy3));
point.translate_by(dx1 + dx2 + dx3, dy1 + dy2 + dy3);
state.sp = 0;
break;
}
case ClosePath:
path.close();
state.sp = 0;
break;
case CallSubr: {
auto subr_number = pop();
if (static_cast<size_t>(subr_number) >= m_subroutines.size())
return error("Subroutine index out of range");
// Subroutines 0-2 handle the flex feature.
if (subr_number == 0) {
if (state.flex_index != 14)
break;
auto& flex = state.flex_sequence;
path.cubic_bezier_curve_to(
{ flex[2], flex[3] },
{ flex[4], flex[5] },
{ flex[6], flex[7] });
path.cubic_bezier_curve_to(
{ flex[8], flex[9] },
{ flex[10], flex[11] },
{ flex[12], flex[13] });
state.flex_feature = false;
state.sp = 0;
} else if (subr_number == 1) {
state.flex_feature = true;
state.flex_index = 0;
state.sp = 0;
} else if (subr_number == 2) {
state.sp = 0;
} else {
auto subr = m_subroutines[subr_number];
if (subr.is_empty())
return error("Empty subroutine");
TRY(parse_glyph(subr, state));
}
break;
}
case Return:
break;
case Extended: {
TRY(require(1));
switch (data[++i]) {
case DotSection:
case VStem3:
case HStem3:
case Seac:
// FIXME: Do something with these?
state.sp = 0;
break;
case Div: {
auto num2 = pop();
auto num1 = pop();
TRY(push(num2 ? num1 / num2 : 0.0f));
break;
}
case CallOtherSubr: {
auto othersubr_number = pop();
auto n = static_cast<int>(pop());
if (othersubr_number == 0) {
state.postscript_stack[state.postscript_sp++] = pop();
state.postscript_stack[state.postscript_sp++] = pop();
pop();
} else if (othersubr_number == 3) {
state.postscript_stack[state.postscript_sp++] = 3;
} else {
for (int i = 0; i < n; ++i)
state.postscript_stack[state.postscript_sp++] = pop();
}
(void)othersubr_number;
break;
}
case Pop:
TRY(push(state.postscript_stack[--state.postscript_sp]));
break;
case SetCurrentPoint: {
auto y = pop();
auto x = pop();
state.point = { x, y };
path.move_to(state.point);
state.sp = 0;
break;
}
default:
return error(DeprecatedString::formatted("Unhandled command: 12 {}", data[i]));
}
break;
}
case HSbW: {
auto wx = pop();
auto sbx = pop();
state.glyph.width = wx;
state.point = { sbx, 0.0f };
state.sp = 0;
break;
}
case EndChar:
break;
case RMoveTo: {
auto dy = pop();
auto dx = pop();
state.point.translate_by(dx, dy);
if (state.flex_feature) {
state.flex_sequence[state.flex_index++] = state.point.x();
state.flex_sequence[state.flex_index++] = state.point.y();
} else {
path.move_to(state.point);
}
state.sp = 0;
break;
}
case HMoveTo: {
auto dx = pop();
state.point.translate_by(dx, 0.0f);
if (state.flex_feature) {
state.flex_sequence[state.flex_index++] = state.point.x();
state.flex_sequence[state.flex_index++] = state.point.y();
} else {
path.move_to(state.point);
}
state.sp = 0;
break;
}
case VHCurveTo: {
auto dx3 = pop();
auto dy2 = pop();
auto dx2 = pop();
auto dy1 = pop();
auto& point = state.point;
path.cubic_bezier_curve_to(
point + Gfx::FloatPoint(0.0f, dy1),
point + Gfx::FloatPoint(dx2, dy1 + dy2),
point + Gfx::FloatPoint(dx2 + dx3, dy1 + dy2));
point.translate_by(dx2 + dx3, dy1 + dy2);
state.sp = 0;
break;
}
case HVCurveTo: {
auto dy3 = pop();
auto dy2 = pop();
auto dx2 = pop();
auto dx1 = pop();
auto& point = state.point;
path.cubic_bezier_curve_to(
point + Gfx::FloatPoint(dx1, 0.0f),
point + Gfx::FloatPoint(dx1 + dx2, dy2),
point + Gfx::FloatPoint(dx1 + dx2, dy2 + dy3));
point.translate_by(dx1 + dx2, dy2 + dy3);
state.sp = 0;
break;
}
default:
return error(DeprecatedString::formatted("Unhandled command: {}", v));
}
}
}
return state.glyph;
TRY(font_program->parse_encrypted_portion(decrypted));
return font_program;
}
PDFErrorOr<void> PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffer)
@ -468,7 +74,7 @@ PDFErrorOr<void> PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffe
if (!seek_name(reader, "Subrs"))
return error("Missing subroutine array");
m_subroutines = TRY(parse_subroutines(reader));
auto subroutines = TRY(parse_subroutines(reader));
if (!seek_name(reader, "CharStrings"))
return error("Missing char strings array");
@ -486,10 +92,10 @@ PDFErrorOr<void> PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffe
if (rd == "-|" || rd == "RD") {
auto line = TRY(decrypt(reader.bytes().slice(reader.offset(), encrypted_size), m_encryption_key, m_lenIV));
reader.move_by(encrypted_size);
auto name_mapping = m_encoding->name_mapping();
auto name_mapping = encoding()->name_mapping();
auto char_code = name_mapping.ensure(word.substring_view(1));
GlyphParserState state;
m_glyph_map.set(char_code, TRY(parse_glyph(line, state)));
TRY(add_glyph(char_code, TRY(parse_glyph(line, subroutines, state))));
}
}
}
@ -497,7 +103,7 @@ PDFErrorOr<void> PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffe
return {};
}
PDFErrorOr<Vector<ByteBuffer>> PS1FontProgram::parse_subroutines(Reader& reader)
PDFErrorOr<Vector<ByteBuffer>> PS1FontProgram::parse_subroutines(Reader& reader) const
{
if (!reader.matches_number())
return error("Expected array length");
@ -634,18 +240,4 @@ bool PS1FontProgram::seek_name(Reader& reader, DeprecatedString const& name)
return false;
}
Error PS1FontProgram::error(
DeprecatedString const& message
#ifdef PDF_DEBUG
,
SourceLocation loc
#endif
)
{
#ifdef PDF_DEBUG
dbgln("\033[31m{} Type 1 font error: {}\033[0m", loc, message);
#endif
return Error { Error::Type::MalformedPDF, message };
}
}