/* * Copyright (c) 2022, Julian Offenhäuser * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include 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, Div = 12, CallOtherSubr = 16, Pop, SetCurrentPoint = 33, }; PDFErrorOr PS1FontProgram::create(ReadonlyBytes const& bytes, RefPtr encoding, size_t cleartext_length, size_t encrypted_length) { Reader reader(bytes); if (reader.remaining() == 0) return error("Empty font program"); reader.move_to(0); if (reader.remaining() < 2 || !reader.matches("%!")) return error("Not a font program"); if (!seek_name(reader, CommonNames::Encoding)) return error("Missing encoding array"); if (encoding) { // 9.6.6.2 Encodings for Type 1 Fonts: // An Encoding entry may override a Type 1 font’s mapping from character codes to character names. m_encoding = encoding; } else { if (TRY(parse_word(reader)) == "StandardEncoding") { m_encoding = Encoding::standard_encoding(); } else { HashMap descriptors; while (reader.remaining()) { auto word = TRY(parse_word(reader)); if (word == "readonly") { break; } else if (word == "dup") { u32 char_code = TRY(parse_int(reader)); auto name = TRY(parse_word(reader)); descriptors.set(char_code, { name.starts_with('/') ? name.substring_view(1) : name.view(), char_code }); } } m_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] }; } else { m_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 PS1FontProgram::rasterize_glyph(u32 char_code, float width) { auto path = build_char(char_code, width); auto bounding_box = path.bounding_box().size(); u32 w = (u32)ceilf(bounding_box.width()) + 1; u32 h = (u32)ceilf(bounding_box.height()) + 1; Gfx::PathRasterizer rasterizer(Gfx::IntSize(w, h)); rasterizer.draw_path(path); return rasterizer.accumulate(); } Gfx::Path PS1FontProgram::build_char(u32 char_code, float width) { 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); // 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::parse_glyph(ReadonlyBytes const& data, GlyphParserState& state) { auto push = [&](float value) -> PDFErrorOr { 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 { 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(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: // 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(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; } PDFErrorOr PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffer) { Reader reader(buffer); if (seek_name(reader, "lenIV")) m_lenIV = TRY(parse_int(reader)); if (!seek_name(reader, "Subrs")) return error("Missing subroutine array"); m_subroutines = TRY(parse_subroutines(reader)); if (!seek_name(reader, "CharStrings")) return error("Missing char strings array"); while (reader.remaining()) { auto word = TRY(parse_word(reader)); VERIFY(!word.is_empty()); if (word == "end") break; if (word[0] == '/') { auto encrypted_size = TRY(parse_int(reader)); auto rd = TRY(parse_word(reader)); 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 char_code = name_mapping.ensure(word.substring_view(1)); GlyphParserState state; m_glyph_map.set(char_code, TRY(parse_glyph(line, state))); } } } return {}; } PDFErrorOr> PS1FontProgram::parse_subroutines(Reader& reader) { if (!reader.matches_number()) return error("Expected array length"); auto length = TRY(parse_int(reader)); VERIFY(length <= 1024); Vector array; TRY(array.try_resize(length)); while (reader.remaining()) { auto word = TRY(parse_word(reader)); if (word.is_empty()) VERIFY(0); if (word == "dup") { auto index = TRY(parse_int(reader)); auto entry = TRY(parse_word(reader)); if (entry.is_empty()) return error("Empty array entry"); if (index >= length) return error("Array index out of bounds"); if (isdigit(entry[0])) { auto maybe_encrypted_size = entry.to_int(); if (!maybe_encrypted_size.has_value()) return error("Malformed array"); auto rd = TRY(parse_word(reader)); if (rd == "-|" || rd == "RD") { array[index] = TRY(decrypt(reader.bytes().slice(reader.offset(), maybe_encrypted_size.value()), m_encryption_key, m_lenIV)); reader.move_by(maybe_encrypted_size.value()); } } else { array[index] = TRY(ByteBuffer::copy(entry.bytes())); } } else if (word == "index") { break; } } return array; } PDFErrorOr> PS1FontProgram::parse_number_array(Reader& reader, size_t length) { Vector array; TRY(array.try_resize(length)); reader.consume_whitespace(); if (!reader.consume('[')) return error("Expected array to start with '['"); reader.consume_whitespace(); for (size_t i = 0; i < length; ++i) array.at(i) = TRY(parse_float(reader)); if (!reader.consume(']')) return error("Expected array to end with ']'"); return array; } PDFErrorOr PS1FontProgram::parse_word(Reader& reader) { reader.consume_whitespace(); auto start = reader.offset(); reader.move_while([&](char c) { return !reader.matches_whitespace() && c != '[' && c != ']'; }); auto end = reader.offset(); if (reader.matches_whitespace()) reader.consume(); return StringView(reader.bytes().data() + start, end - start); } PDFErrorOr PS1FontProgram::parse_float(Reader& reader) { auto word = TRY(parse_word(reader)); return strtof(DeprecatedString(word).characters(), nullptr); } PDFErrorOr PS1FontProgram::parse_int(Reader& reader) { auto maybe_int = TRY(parse_word(reader)).to_int(); if (!maybe_int.has_value()) return error("Invalid int"); return maybe_int.value(); } PDFErrorOr PS1FontProgram::decrypt(ReadonlyBytes const& encrypted, u16 key, size_t skip) { auto decrypted = TRY(ByteBuffer::create_uninitialized(encrypted.size() - skip)); u16 R = key; u16 c1 = 52845; u16 c2 = 22719; for (size_t i = 0; i < encrypted.size(); ++i) { u8 C = encrypted[i]; u8 P = C ^ (R >> 8); R = (C + R) * c1 + c2; if (i >= skip) decrypted[i - skip] = P; } return decrypted; } bool PS1FontProgram::seek_name(Reader& reader, DeprecatedString const& name) { auto start = reader.offset(); reader.move_to(0); while (reader.remaining()) { if (reader.consume('/') && reader.matches(name.characters())) { // Skip name reader.move_while([&](char) { return reader.matches_regular_character(); }); reader.consume_whitespace(); return true; } } // Jump back to where we started reader.move_to(start); 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 }; } }