Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1c1a486
PDFBOX-3293: parse the TrueType hinting tables (cvt, fpgm, prep, gasp)
pcorless Sep 9, 2026
6818432
PDFBOX-3293: TrueType bytecode interpreter
pcorless Sep 9, 2026
5af2e1c
PDFBOX-3293: grid-fit glyph outlines in FontBox
pcorless Sep 9, 2026
d190752
PDFBOX-3293: use grid-fitted glyphs in the render path
pcorless Sep 9, 2026
f99b7f1
PDFBOX-3293: verify hinted output against FreeType
pcorless Sep 9, 2026
3166076
PDFBOX-3293 remove duplicate LiberationSans-Regular.ttf resource and …
pcorless Sep 17, 2026
56666ee
PDFBOX-3293 move enabling hinting out to PDFRenderer instead of in Tr…
pcorless Sep 17, 2026
a9f4ef8
PDFBOX-3293 hookup hinting in the debugger
pcorless Sep 18, 2026
c8b9a0e
PDFBOX-3293: skip hinting below head.lowestRecPPEM
pcorless Sep 19, 2026
038c197
PDFBOX-3293: ODD/EVEN round with the current round state; S45ROUND in…
pcorless Sep 19, 2026
f3b9f58
PDFBOX-3293: gate SHPIX like DELTAP under v40 backward compatibility
pcorless Sep 19, 2026
c197ec1
PDFBOX-3293: bound function bodies and reject malformed control flow
pcorless Sep 19, 2026
44a0715
PDFBOX-3293: pin hintingPpem under composed, rotated and anisotropic …
pcorless Sep 19, 2026
b11f250
PDFBOX-3293 clean up working replacing 'oracle' for 'reference'
pcorless Sep 23, 2026
647f37e
PDFBOX-3293: match FreeType 2.13.2 hinting exactly
pcorless Sep 25, 2026
edde8bc
PDFBOX-3293: bound push operands by the function end; one FreeType ta…
pcorless Sep 25, 2026
a3530a4
PDFBOX-3293: size the fpgm/prep loop budget like FreeType; hinting sm…
pcorless Sep 29, 2026
b63f3f1
PDFBOX-3293: detect fonts whose bytecode builds the glyphs, without a…
pcorless Sep 30, 2026
41d7719
PDFBOX-3293: catch only HintingException; bounds-check bytecode point…
pcorless Sep 30, 2026
d2de203
PDFBOX-3293: cover the untested interpreter paths; fix ALIGNPTS and t…
pcorless Sep 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ protected BufferedImage doInBackground() throws IOException

PDFRenderer renderer = new PDFRenderer(document);
renderer.setSubsamplingAllowed(ViewMenu.isAllowSubsampling());
//renderer.setHintingEnabled(ViewMenu.isHintingEnabled())
renderer.setHintingEnabled(ViewMenu.isHintingEnabled());

long t0 = System.nanoTime();
BufferedImage image = renderer.renderImage(pageIndex, scale, ImageTypeMenu.getImageType(), RenderDestinationMenu.getRenderDestination());
Expand Down
157 changes: 157 additions & 0 deletions fontbox/src/main/java/org/apache/fontbox/ttf/BytecodeStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.fontbox.ttf;

/**
* A bounds-checked cursor over a TrueType bytecode program (fpgm, prep, a glyph instruction stream, or
* a function body). It owns the program counter so individual opcode handlers never index the array by
* hand - this removes a whole class of off-by-one and overrun bugs and is trivially testable on its
* own. Reads past the end throw {@link HintingException} so the per-glyph fallback can catch cleanly.
*
* @author Apache PDFBox
*/
class BytecodeStream
{
private final byte[] code;
private int ip;
private int instructionStart;
// exclusive upper bound for the program counter; a function body ends at its ENDF
private int limit;

/**
* @param code the bytecode program; not copied
*/
public BytecodeStream(byte[] code)
{
this.code = code != null ? code : new byte[0];
this.limit = this.code.length;
}

/**
* @return the underlying bytecode array (not copied); used to record function entry points
*/
public byte[] getCode()
{
return code;
}

/**
* @return true if there is at least one more byte to read
*/
public boolean hasNext()
{
return ip < limit;
}

/**
* Restricts execution to {@code [0, end]}: the stream is exhausted after the byte at {@code end}
* (the {@code ENDF} of a function body) and a seek past it is an error, as a jump out of a
* function body is in FreeType.
*
* @param end the position of the last byte that may be executed
*/
public void setEnd(int end)
{
this.limit = Math.min(code.length, end + 1);
}

/**
* @return the current program-counter position
*/
public int position()
{
return ip;
}

/**
* Records the current position as the start of the instruction about to be read. Relative jumps
* ({@code JMPR}/{@code JROT}/{@code JROF}) are measured from here.
*/
public void markInstructionStart()
{
instructionStart = ip;
}

/**
* @return the position recorded by the most recent {@link #markInstructionStart()}
*/
public int instructionStart()
{
return instructionStart;
}

/**
* Moves the program counter to an absolute position.
*
* @param position the new position, within {@code [0, length]}
* @throws HintingException if the position is out of range
*/
public void seek(int position)
{
if (position < 0 || position > limit)
{
throw new HintingException(
"bytecode seek out of range: " + position + " of " + limit);
}
ip = position;
}

/**
* Advances the program counter by a relative amount (may be negative).
*
* @param delta the number of bytes to skip
* @throws HintingException if the result is out of range
*/
public void skip(int delta)
{
seek(ip + delta);
}

/**
* Reads the next byte as an unsigned 0-255 value (an opcode, or push operand). Like
* {@link #seek(int)}, reads honour a function body's end, so a push reached by a jump into the
* middle of another push's operands cannot take bytes past the body's {@code ENDF}.
*
* @return the next unsigned byte
* @throws HintingException if the stream is exhausted
*/
public int nextByte()
{
if (ip >= limit)
{
throw new HintingException("bytecode read past end at " + ip);
}
return code[ip++] & 0xFF;
}

/**
* Reads the next two bytes as a signed big-endian 16-bit word.
*
* @return the next signed word
* @throws HintingException if fewer than two bytes remain
*/
public int nextWord()
{
if (ip + 1 >= limit)
{
throw new HintingException("bytecode word read past end at " + ip);
}
int hi = code[ip++] & 0xFF;
int lo = code[ip++] & 0xFF;
return (short) ((hi << 8) | lo);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.fontbox.ttf;

import java.io.IOException;

/**
* The 'prep' (Control Value Program, also known as the pre-program) table. It holds TrueType hinting
* bytecode that is executed whenever the point size or transform changes, to prepare the Control Value
* Table and graphics state for the new ppem. The bytecode is stored here as raw bytes and run by the
* interpreter.
*
* @author Apache PDFBox
*/
public class ControlValueProgramTable extends TTFTable
{
/**
* A tag that identifies this table type.
*/
public static final String TAG = "prep";

private byte[] program;

ControlValueProgramTable()
{
}

/**
* This will read the required data from the stream.
*
* @param ttf The font that is being read.
* @param data The stream to read the data from.
* @throws IOException If there is an error reading the data.
*/
@Override
void read(TrueTypeFont ttf, TTFDataStream data) throws IOException
{
program = data.read((int) getLength());
initialized = true;
}

/**
* Returns the raw control value program bytecode.
*
* @return the bytecode of the pre-program
*/
public byte[] getProgram()
{
return program;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.fontbox.ttf;

import java.io.IOException;

/**
* The 'cvt ' (Control Value) table. It holds an array of reference values - stem widths, heights and
* similar control measurements - used by the TrueType hinting bytecode. The values are stored here in
* raw font units (signed FWords); they are scaled to the active ppem by the interpreter, not at parse
* time.
*
* @author Apache PDFBox
*/
public class ControlValueTable extends TTFTable
{
/**
* A tag that identifies this table type.
*/
public static final String TAG = "cvt ";

private int[] values;

ControlValueTable()
{
}

/**
* This will read the required data from the stream.
*
* @param ttf The font that is being read.
* @param data The stream to read the data from.
* @throws IOException If there is an error reading the data.
*/
@Override
void read(TrueTypeFont ttf, TTFDataStream data) throws IOException
{
int count = (int) (getLength() / 2);
int[] cvt = new int[count];
for (int i = 0; i < count; i++)
{
cvt[i] = data.readSignedShort();
}
values = cvt;
initialized = true;
}

/**
* Returns the raw control values in font units (FWords). The interpreter scales these to the
* active ppem.
*
* @return the control values in font units
*/
public int[] getValues()
{
return values;
}

/**
* Returns the number of control values in this table.
*
* @return the entry count
*/
public int getValueCount()
{
return values != null ? values.length : 0;
}
}
Loading