Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial fix for issue #68: Managing GraphicsState with a Stack is unsafe #69

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -105,10 +105,18 @@ public class VectorGraphics2D extends Graphics2D implements Cloneable {
private boolean disposed;

private GraphicsState state;

/** Parent Command linked to all other Commands created by this graphics object. This will
* be used to track the appropriate GraphicsState for each Command during Document processing.
*/
private CreateCommand parentCommand;

public VectorGraphics2D() {
this.commands = new MutableCommandSequence();
emit(new CreateCommand(this));
// Note that this instance will have itself as a parent (via the emit() call). That self
// relationship will be used to mark the top-most instance.
parentCommand = new CreateCommand(this);
emit(parentCommand);
GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice graphicsDevice = null;
if (!graphicsEnvironment.isHeadlessInstance()) {
Expand All @@ -132,6 +140,10 @@ public VectorGraphics2D() {
public Object clone() throws CloneNotSupportedException {
VectorGraphics2D clone = (VectorGraphics2D) super.clone();
clone.state = (GraphicsState) state.clone();
// Create a clone.parentCommand linked to the clone Graphics object,
// but with the current instance's command as its parent.
clone.parentCommand = new CreateCommand(clone);
clone.parentCommand.setParent(parentCommand);
return clone;
}

Expand Down Expand Up @@ -527,7 +539,7 @@ public Graphics create() {
VectorGraphics2D clone = null;
try {
clone = (VectorGraphics2D) this.clone();
emit(new CreateCommand(clone));
emit(clone.parentCommand);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
Expand Down Expand Up @@ -788,6 +800,9 @@ public void setXORMode(Color c1) {
}

private void emit(Command<?> command) {
// Patch in the instance's parentCommand
// TODO: consider whether this should be done via the Command's constructor instead.
command.setParent(parentCommand);
commands.add(command);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

public abstract class Command<T> {
private final T value;
private CreateCommand parent;

public Command(T value) {
this.value = value;
Expand All @@ -34,6 +35,14 @@ public T getValue() {
return value;
}

public CreateCommand getParent() {
return parent;
}

public void setParent(CreateCommand parent) {
this.parent = parent;
}

@Override
public String toString() {
return String.format((Locale) null, "%s[value=%s]",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,22 @@
*/
package de.erichseifert.vectorgraphics2d.intermediate.commands;

import de.erichseifert.vectorgraphics2d.GraphicsState;
import de.erichseifert.vectorgraphics2d.VectorGraphics2D;

public class CreateCommand extends StateCommand<VectorGraphics2D> {
private GraphicsState processingState;

public CreateCommand(VectorGraphics2D graphics) {
super(graphics);
}

public GraphicsState getProcessingState() {
return processingState;
}

public void setProcessingState(GraphicsState processingState) {
this.processingState = processingState;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ protected List<Command<?>> filter(Command<?> command) {
}
relativeTransform.concatenate(absoluteTransform);
TransformCommand transformCommand = new TransformCommand(relativeTransform);
transformCommand.setParent(command.getParent());
return Collections.<Command<?>>singletonList(transformCommand);
}
return Collections.<Command<?>>singletonList(command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ private DrawImageCommand getDrawImageCommand(FillShapeCommand shapeCommand, SetP
imageGraphics.fill(shape);
imageGraphics.dispose();

return new DrawImageCommand(image, imageWidth, imageHeight, x, y, width, height);
DrawImageCommand drawImageCommand = new DrawImageCommand(image, imageWidth, imageHeight, x, y, width, height);
drawImageCommand.setParent(paintCommand.getParent());
return drawImageCommand;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ protected List<Command<?>> filter(Command<?> command) {
group = new Group();
}
group.add(command);

// Update the group's parent as we go; unlike other commands, the group must be associated
// with the *final* command's state as it is created after all those commands are applied.
group.setParent(command.getParent());
return null;
}
return Collections.<Command<?>>singletonList(command);
Expand Down
49 changes: 28 additions & 21 deletions src/main/java/de/erichseifert/vectorgraphics2d/pdf/PDFDocument.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,13 @@ class PDFDocument extends SizedDocument {
private Resources resources;
private final Map<Integer, PDFObject> images;

private final Stack<GraphicsState> states;
private final GraphicsState defaultState;
private boolean transformed;

PDFDocument(CommandSequence commands, PageSize pageSize, boolean compressed) {
super(pageSize, compressed);

states = new Stack<>();
states.push(new GraphicsState());
defaultState = new GraphicsState();

objects = new LinkedList<>();
crossReferences = new HashMap<>();
Expand All @@ -131,10 +130,6 @@ class PDFDocument extends SizedDocument {
close();
}

private GraphicsState getCurrentState() {
return states.peek();
}

/**
* Initializes the document and returns a {@code Stream} representing the contents.
* @return {@code Stream} to which the contents are written.
Expand All @@ -159,7 +154,7 @@ private Stream initPage() {
try {
FormattingWriter string = new FormattingWriter(contents, CHARSET, EOL);
string.writeln("q");
string.writeln(getOutput(getCurrentState().getColor()));
string.writeln(getOutput(defaultState.getColor()));
string.write(MM_IN_UNITS).write(" 0 0 ").write(-MM_IN_UNITS)
.write(" 0 ").write(getPageSize().getHeight()*MM_IN_UNITS)
.writeln(" cm");
Expand All @@ -173,7 +168,7 @@ private Stream initPage() {
page.dict.put("Resources", resources);

// Create initial font
Font font = getCurrentState().getFont();
Font font = defaultState.getFont();
String fontResourceId = resources.getId(font);
float fontSize = font.getSize2D();
setFont(fontResourceId, fontSize, contents);
Expand Down Expand Up @@ -493,7 +488,8 @@ private byte[] toBytes(Command<?> command) {
if (command instanceof Group) {
Group c = (Group) command;
applyStateCommands(c.getValue());
s = getOutput(getCurrentState(), resources, !transformed);
GraphicsState state = command.getParent().getProcessingState();
s = getOutput(state, resources, !transformed);
transformed = true;
} else if (command instanceof DrawShapeCommand) {
DrawShapeCommand c = (DrawShapeCommand) command;
Expand Down Expand Up @@ -538,43 +534,54 @@ private byte[] toBytes(Command<?> command) {

private void applyStateCommands(List<Command<?>> commands) {
for (Command<?> command : commands) {
GraphicsState state = command.getParent().getProcessingState();
if (command instanceof SetHintCommand) {
SetHintCommand c = (SetHintCommand) command;
getCurrentState().getHints().put(c.getKey(), c.getValue());
state.getHints().put(c.getKey(), c.getValue());
} else if (command instanceof SetBackgroundCommand) {
SetBackgroundCommand c = (SetBackgroundCommand) command;
getCurrentState().setBackground(c.getValue());
state.setBackground(c.getValue());
} else if (command instanceof SetColorCommand) {
SetColorCommand c = (SetColorCommand) command;
getCurrentState().setColor(c.getValue());
state.setColor(c.getValue());
} else if (command instanceof SetPaintCommand) {
SetPaintCommand c = (SetPaintCommand) command;
getCurrentState().setPaint(c.getValue());
state.setPaint(c.getValue());
} else if (command instanceof SetStrokeCommand) {
SetStrokeCommand c = (SetStrokeCommand) command;
getCurrentState().setStroke(c.getValue());
state.setStroke(c.getValue());
} else if (command instanceof SetFontCommand) {
SetFontCommand c = (SetFontCommand) command;
getCurrentState().setFont(c.getValue());
state.setFont(c.getValue());
} else if (command instanceof SetTransformCommand) {
throw new UnsupportedOperationException("The PDF format has no means of setting the transformation matrix.");
} else if (command instanceof AffineTransformCommand) {
AffineTransformCommand c = (AffineTransformCommand) command;
AffineTransform stateTransform = getCurrentState().getTransform();
AffineTransform stateTransform = state.getTransform();
AffineTransform transformToBeApplied = c.getValue();
stateTransform.concatenate(transformToBeApplied);
getCurrentState().setTransform(stateTransform);
state.setTransform(stateTransform);
} else if (command instanceof SetClipCommand) {
SetClipCommand c = (SetClipCommand) command;
getCurrentState().setClip(c.getValue());
state.setClip(c.getValue());
} else if (command instanceof CreateCommand) {
CreateCommand c = (CreateCommand) command;
try {
states.push((GraphicsState) getCurrentState().clone());
// Inject an cloned instance of GraphicsState into the CreateCommand for
// use by all Commands that were created by its Graphics object.
// For the top-most instance (self reference to parent) use a clone of
// the defaultState, otherwise use a clone of the parent's state.
CreateCommand parent = c.getParent();
if (c != parent && state != null) {
c.setProcessingState((GraphicsState) state.clone());
} else {
c.setProcessingState((GraphicsState) defaultState.clone());
}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
} else if (command instanceof DisposeCommand) {
states.pop();
// No-op
}
}
}
Expand Down
Loading