1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
|
# encoding: utf-8
# snapshot.rb : Implements transactional rendering for Prawn
#
# Copyright August 2009, Brad Ediger. All Rights Reserved.
#
# This is free software. Please see the LICENSE and COPYING files for details.
require 'delegate'
module Prawn
class Document
module Snapshot
RollbackTransaction = Class.new(StandardError)
# Call this within a +transaction+ block to roll back the transaction and
# prevent any of its data from being rendered. You must reset the
# y-position yourself if you have performed any drawing operations that
# modify it.
#
def rollback
raise RollbackTransaction
end
# Run a block of drawing operations, to be completed atomically. If
# +rollback+ is called or a RollbackTransaction exception is raised
# inside the block, all actions taken inside the block will be rolled
# back (with the exception of y-position, which you must restore
# yourself).
#
# Returns true on success, or false if the transaction was rolled back.
#
def transaction
snap = take_snapshot
yield
true
rescue RollbackTransaction
restore_snapshot(snap)
false
end
private
# Takes a current snapshot of the document's state, sufficient to
# reconstruct it after it was amended.
#
def take_snapshot
# current_page holds a ref to the Pages dictionary which grows
# monotonically as data is added to the document, so we share that
# between the old and new copies.
{:page_content => state.page.content.deep_copy,
:current_page => state.page.dictionary.deep_copy(share=[:Parent]),
:bounds => bounds.deep_copy,
:page_number => page_number,
:page_kids => state.store.pages.data[:Kids].map{|kid| kid.identifier},
:dests => names? && names.data[:Dests].deep_copy}
end
# Rolls the page state back to the state of the given snapshot.
#
def restore_snapshot(shot)
page = state.page
# Because these objects are referenced by identifier from the Pages
# dictionary, we can't just restore them over the current refs in
# page_content and current_page. We have to restore them over the old
# ones.
page.content = shot[:page_content].identifier
page.content.replace shot[:page_content]
page.dictionary = shot[:current_page].identifier
page.dictionary.replace shot[:current_page]
page.dictionary.data[:Contents] = page.content
self.page_number = shot[:page_number]
state.store.pages.data[:Kids] = shot[:page_kids].map{|id| state.store[id]}
state.store.pages.data[:Count] = shot[:page_kids].size
self.bounds = BoundingBox.restore_deep_copy(shot[:bounds], self)
if shot[:dests]
names.data[:Dests] = shot[:dests]
end
end
end
end
end
|