mirror of
https://github.com/RGBCube/serenity
synced 2025-06-03 12:58:14 +00:00
LibPDF: Augment Type11FontProgram with Type2 capabilities
The Type1FontProgram logic was based on the Adobe Type 1 Font Format; in particular, it implemented the CharStrings Dictionary section (charstring decoding, and most commands). In the case of Type1, these charstrings are read from a PS1 diciontary, with one entry per character in the font's charset. This has served us well for Type1 font rendering. When implementing Type1C font rendering, this wasn't enough. Type1C PDF fonts are specified in embedded CFF (Compact Font File) streams, which also contain a charstring dictionary with an entry for each character in the font's charset. These entries can be slightly different from those in a PS1 Font Program though: depending on a flag in the CFF, the entries will be encoded either in the original charstring format from the Adobe Type 1 Font Format, or in the "Type 2 Charstring Format" (Adobe's Technical Note #1577). This new format is for the most part a super-set of the original, with small differences, all in the name of making the representation as compact as possible: * The glyph's width is not specified via a separate command; instead it's an optional additional argument to the first command of the charstring stream (and even then, it's only the *difference* to a nominal character width specified in the CFF). * The interpretation of a 4-byte number is different from Type 1: in Type 1 this is a 4-byte unsigned integer, whereas in Type 1 it's a fixed decimal with 16 bits of fractional part. * Many commands accept a variable set of arguments, so they can draw more than one line/curve on a single go. These are all retro-compatible with Type 1's commands. All these changes are implemented in this patch in a backwards-compatible way. To ensure Type 1/2 behavior is accessed, a new parameter indicates which behavior is desired when decoding the charstring stream. I also took the chance to centralise some logic that was previously duplicated across the parse_glyph function. Common lambdas capture the logic for moving to, or drawing a line/curve to a given point and updating the glyph state. Similarly, some command logic, including reading parameters, are shared by several commands. Finally, I've re-organised the cases in the main switch to group together related commands.
This commit is contained in:
parent
f06de0fa07
commit
1b90ea7d3a
3 changed files with 224 additions and 119 deletions
|
@ -95,7 +95,7 @@ PDFErrorOr<void> PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffe
|
|||
auto name_mapping = encoding()->name_mapping();
|
||||
auto char_code = name_mapping.ensure(word.substring_view(1));
|
||||
GlyphParserState state;
|
||||
TRY(add_glyph(char_code, TRY(parse_glyph(line, subroutines, state))));
|
||||
TRY(add_glyph(char_code, TRY(parse_glyph(line, subroutines, state, false))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,16 @@ enum Command {
|
|||
Extended,
|
||||
HSbW,
|
||||
EndChar,
|
||||
RMoveTo = 21,
|
||||
HStemHM = 18,
|
||||
Hintmask,
|
||||
Cntrmask,
|
||||
RMoveTo,
|
||||
HMoveTo,
|
||||
VStemHM,
|
||||
RCurveLine,
|
||||
RLineCurve,
|
||||
VVCurveTo,
|
||||
HHCurveTo,
|
||||
VHCurveTo = 30,
|
||||
HVCurveTo
|
||||
};
|
||||
|
@ -99,7 +107,7 @@ Gfx::AffineTransform Type1FontProgram::glyph_transform_to_device_space(Glyph con
|
|||
return transform;
|
||||
}
|
||||
|
||||
PDFErrorOr<Type1FontProgram::Glyph> Type1FontProgram::parse_glyph(ReadonlyBytes const& data, Vector<ByteBuffer> const& subroutines, GlyphParserState& state)
|
||||
PDFErrorOr<Type1FontProgram::Glyph> Type1FontProgram::parse_glyph(ReadonlyBytes const& data, Vector<ByteBuffer> const& subroutines, GlyphParserState& state, bool is_type2)
|
||||
{
|
||||
auto push = [&](float value) -> PDFErrorOr<void> {
|
||||
if (state.sp >= state.stack.size())
|
||||
|
@ -109,10 +117,102 @@ PDFErrorOr<Type1FontProgram::Glyph> Type1FontProgram::parse_glyph(ReadonlyBytes
|
|||
};
|
||||
|
||||
auto pop = [&]() -> float {
|
||||
return state.sp ? state.stack[--state.sp] : 0.0f;
|
||||
return state.stack[--state.sp];
|
||||
};
|
||||
|
||||
auto pop_front = [&]() {
|
||||
auto value = state.stack[0];
|
||||
--state.sp;
|
||||
for (size_t i = 0; i < state.sp; i++)
|
||||
state.stack[i] = state.stack[i + 1];
|
||||
return value;
|
||||
};
|
||||
|
||||
auto& path = state.glyph.path;
|
||||
auto& point = state.point;
|
||||
|
||||
// Core operations: move to, line to, curve to
|
||||
auto move_to = [&](float dx, float dy) {
|
||||
point.translate_by(dx, dy);
|
||||
if (is_type2)
|
||||
path.close();
|
||||
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(point);
|
||||
}
|
||||
};
|
||||
|
||||
auto line_to = [&](float dx, float dy) {
|
||||
point.translate_by(dx, dy);
|
||||
path.line_to(point);
|
||||
};
|
||||
|
||||
auto cube_bezier_curve_to = [&](float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) {
|
||||
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);
|
||||
};
|
||||
|
||||
// Shared operator logic
|
||||
auto rline_to = [&]() {
|
||||
auto dx = pop_front();
|
||||
auto dy = pop_front();
|
||||
line_to(dx, dy);
|
||||
};
|
||||
|
||||
auto hvline_to = [&](bool horizontal) {
|
||||
while (state.sp > 0) {
|
||||
auto d = pop_front();
|
||||
float dx = horizontal ? d : 0;
|
||||
float dy = horizontal ? 0 : d;
|
||||
line_to(dx, dy);
|
||||
horizontal = !horizontal;
|
||||
}
|
||||
};
|
||||
|
||||
auto rrcurve_to = [&]() {
|
||||
auto dx1 = pop_front();
|
||||
auto dy1 = pop_front();
|
||||
auto dx2 = pop_front();
|
||||
auto dy2 = pop_front();
|
||||
auto dx3 = pop_front();
|
||||
auto dy3 = pop_front();
|
||||
cube_bezier_curve_to(dx1, dy1, dx2, dy2, dx3, dy3);
|
||||
};
|
||||
|
||||
auto hvcurve_to = [&](bool first_tangent_horizontal) {
|
||||
while (state.sp > 0) {
|
||||
auto d1 = pop_front();
|
||||
auto dx2 = pop_front();
|
||||
auto dy2 = pop_front();
|
||||
auto d3 = pop_front();
|
||||
float d4 = state.sp == 1 ? pop_front() : 0;
|
||||
|
||||
auto dx1 = first_tangent_horizontal ? d1 : 0;
|
||||
auto dy1 = first_tangent_horizontal ? 0 : d1;
|
||||
auto dx3 = first_tangent_horizontal ? d4 : d3;
|
||||
auto dy3 = first_tangent_horizontal ? d3 : d4;
|
||||
cube_bezier_curve_to(dx1, dy1, dx2, dy2, dx3, dy3);
|
||||
first_tangent_horizontal = !first_tangent_horizontal;
|
||||
}
|
||||
};
|
||||
|
||||
// Potential font width parsing for some commands (type2 only)
|
||||
bool is_first_command = true;
|
||||
enum EvenOrOdd {
|
||||
Even,
|
||||
Odd
|
||||
};
|
||||
auto maybe_read_width = [&](EvenOrOdd required_argument_count) {
|
||||
if (!is_type2 || !is_first_command || state.sp % 2 != required_argument_count)
|
||||
return;
|
||||
state.glyph.width = pop_front();
|
||||
state.glyph.width_specified = true;
|
||||
};
|
||||
|
||||
// Parse the stream of parameters and commands that make up a glyph outline.
|
||||
for (size_t i = 0; i < data.size(); ++i) {
|
||||
|
@ -129,6 +229,11 @@ PDFErrorOr<Type1FontProgram::Glyph> Type1FontProgram::parse_glyph(ReadonlyBytes
|
|||
int b = data[++i];
|
||||
int c = data[++i];
|
||||
int d = data[++i];
|
||||
if (is_type2) {
|
||||
auto integer = float((a << 8) | b);
|
||||
auto fraction = float((c << 8) | d) / AK::NumericLimits<u16>::max();
|
||||
TRY(push(integer + fraction));
|
||||
} else
|
||||
TRY(push((a << 24) + (b << 16) + (c << 8) + d));
|
||||
} else if (v >= 251) {
|
||||
TRY(require(1));
|
||||
|
@ -143,71 +248,79 @@ PDFErrorOr<Type1FontProgram::Glyph> Type1FontProgram::parse_glyph(ReadonlyBytes
|
|||
} else {
|
||||
// Not a parameter but a command byte.
|
||||
switch (v) {
|
||||
|
||||
// hints operators
|
||||
case HStemHM:
|
||||
state.n_hints++;
|
||||
[[fallthrough]];
|
||||
case HStem:
|
||||
maybe_read_width(Odd);
|
||||
state.sp = 0;
|
||||
break;
|
||||
|
||||
case VStemHM:
|
||||
state.n_hints++;
|
||||
[[fallthrough]];
|
||||
case VStem:
|
||||
maybe_read_width(Odd);
|
||||
state.sp = 0;
|
||||
break;
|
||||
|
||||
case Hintmask:
|
||||
case Cntrmask: {
|
||||
maybe_read_width(Odd);
|
||||
auto hint_bytes = (state.n_hints + 8 - 1) / 8;
|
||||
TRY(require(hint_bytes));
|
||||
i += hint_bytes;
|
||||
break;
|
||||
}
|
||||
|
||||
// move-to operators
|
||||
case RMoveTo: {
|
||||
maybe_read_width(Odd);
|
||||
auto dy = pop();
|
||||
auto dx = pop();
|
||||
move_to(dx, dy);
|
||||
state.sp = 0;
|
||||
break;
|
||||
}
|
||||
case HMoveTo: {
|
||||
maybe_read_width(Even);
|
||||
auto dx = pop();
|
||||
move_to(dx, 0);
|
||||
state.sp = 0;
|
||||
break;
|
||||
}
|
||||
case VMoveTo: {
|
||||
maybe_read_width(Even);
|
||||
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);
|
||||
}
|
||||
move_to(0, dy);
|
||||
state.sp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// line-to operators
|
||||
case RLineTo: {
|
||||
auto dy = pop();
|
||||
auto dx = pop();
|
||||
|
||||
state.point.translate_by(dx, dy);
|
||||
path.line_to(state.point);
|
||||
while (state.sp >= 2)
|
||||
rline_to();
|
||||
state.sp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case HLineTo: {
|
||||
auto dx = pop();
|
||||
|
||||
state.point.translate_by(dx, 0.0f);
|
||||
path.line_to(state.point);
|
||||
hvline_to(true);
|
||||
state.sp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case VLineTo: {
|
||||
auto dy = pop();
|
||||
|
||||
state.point.translate_by(0.0f, dy);
|
||||
path.line_to(state.point);
|
||||
hvline_to(false);
|
||||
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;
|
||||
while (state.sp >= 6)
|
||||
rrcurve_to();
|
||||
VERIFY(state.sp == 0);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -246,11 +359,11 @@ PDFErrorOr<Type1FontProgram::Glyph> Type1FontProgram::parse_glyph(ReadonlyBytes
|
|||
} else if (subr_number == 2) {
|
||||
state.sp = 0;
|
||||
} else {
|
||||
auto subr = subroutines[subr_number];
|
||||
auto const& subr = subroutines[subr_number];
|
||||
if (subr.is_empty())
|
||||
return error("Empty subroutine");
|
||||
|
||||
TRY(parse_glyph(subr, subroutines, state));
|
||||
TRY(parse_glyph(subr, subroutines, state, is_type2));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -278,21 +391,10 @@ PDFErrorOr<Type1FontProgram::Glyph> Type1FontProgram::parse_glyph(ReadonlyBytes
|
|||
}
|
||||
|
||||
case CallOtherSubr: {
|
||||
auto othersubr_number = pop();
|
||||
[[maybe_unused]] 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;
|
||||
}
|
||||
|
||||
|
@ -321,84 +423,85 @@ PDFErrorOr<Type1FontProgram::Glyph> Type1FontProgram::parse_glyph(ReadonlyBytes
|
|||
auto sbx = pop();
|
||||
|
||||
state.glyph.width = wx;
|
||||
state.glyph.width_specified = true;
|
||||
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;
|
||||
case EndChar: {
|
||||
maybe_read_width(Odd);
|
||||
if (is_type2)
|
||||
path.close();
|
||||
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);
|
||||
hvcurve_to(false);
|
||||
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);
|
||||
hvcurve_to(true);
|
||||
state.sp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case VVCurveTo: {
|
||||
float dx1 = 0;
|
||||
if (state.sp % 2 == 1)
|
||||
dx1 = pop_front();
|
||||
do {
|
||||
auto dy1 = pop_front();
|
||||
auto dx2 = pop_front();
|
||||
auto dy2 = pop_front();
|
||||
auto dy3 = pop_front();
|
||||
cube_bezier_curve_to(dx1, dy1, dx2, dy2, 0, dy3);
|
||||
dx1 = 0;
|
||||
} while (state.sp >= 4);
|
||||
state.sp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case HHCurveTo: {
|
||||
float dy1 = 0;
|
||||
if (state.sp % 2 == 1)
|
||||
dy1 = pop_front();
|
||||
do {
|
||||
auto dx1 = pop_front();
|
||||
auto dx2 = pop_front();
|
||||
auto dy2 = pop_front();
|
||||
auto dx3 = pop_front();
|
||||
cube_bezier_curve_to(dx1, dy1, dx2, dy2, dx3, 0);
|
||||
dy1 = 0;
|
||||
} while (state.sp >= 4);
|
||||
state.sp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case RCurveLine: {
|
||||
while (state.sp >= 8) {
|
||||
rrcurve_to();
|
||||
}
|
||||
rline_to();
|
||||
state.sp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case RLineCurve: {
|
||||
while (state.sp >= 8) {
|
||||
rline_to();
|
||||
}
|
||||
rrcurve_to();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return error(DeprecatedString::formatted("Unhandled command: {}", v));
|
||||
}
|
||||
|
||||
is_first_command = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@ public:
|
|||
protected:
|
||||
struct Glyph {
|
||||
Gfx::Path path;
|
||||
float width;
|
||||
float width { 0 };
|
||||
bool width_specified { false };
|
||||
};
|
||||
|
||||
struct GlyphParserState {
|
||||
|
@ -41,12 +42,13 @@ protected:
|
|||
|
||||
size_t sp { 0 };
|
||||
Array<float, 24> stack;
|
||||
u8 n_hints { 0 };
|
||||
|
||||
size_t postscript_sp { 0 };
|
||||
Array<float, 24> postscript_stack;
|
||||
};
|
||||
|
||||
static PDFErrorOr<Glyph> parse_glyph(ReadonlyBytes const&, Vector<ByteBuffer> const&, GlyphParserState&);
|
||||
static PDFErrorOr<Glyph> parse_glyph(ReadonlyBytes const&, Vector<ByteBuffer> const&, GlyphParserState&, bool is_type2);
|
||||
|
||||
static Error error(
|
||||
DeprecatedString const& message
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue