1 // ePDFView - A lightweight PDF Viewer.
2 // Copyright (C) 2006, 2007, 2009 Emma's Software.
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 
18 #include <config.h>
19 #include <string.h>
20 #include <gdk/gdk.h>
21 #include "epdfview.h"
22 
23 using namespace ePDFView;
24 
25 static const gint BYTES_PER_PIXEL = 3;
26 
27 ///
28 /// @brief Constructs a new DocumentPage.
29 ///
DocumentPage()30 DocumentPage::DocumentPage ()
31 {
32     m_Selection = NULL;
33     m_Data = NULL;
34     m_HasSelection = FALSE;
35     m_Height = 0;
36     m_LinkList = NULL;
37     m_Width = 0;
38 }
39 
40 ///
41 /// @brief Deletes all dynamically allocated memory for DocumentPage.
42 ///
~DocumentPage()43 DocumentPage::~DocumentPage ()
44 {
45     delete[] m_Data;
46     for ( GList *linkItem = g_list_first (m_LinkList) ;
47           NULL != linkItem ;
48           linkItem = g_list_next (linkItem) )
49     {
50         IDocumentLink *link = (IDocumentLink *)linkItem->data;
51         delete link;
52     }
53     g_list_free (m_LinkList);
54 
55     if(m_Selection)
56         gdk_region_destroy(m_Selection);
57 }
58 
59 ///
60 /// @brief Adds a new link to the page.
61 ///
62 /// When rendering the page, the responsible document will extract the
63 /// links from the page and call this function with each links it find.
64 ///
65 /// @param link The link to add. The DocumentPage class will delete it.
66 ///
67 void
addLink(IDocumentLink * link)68 DocumentPage::addLink (IDocumentLink *link)
69 {
70     m_LinkList = g_list_prepend (m_LinkList, link);
71 }
72 
73 ///
74 /// @brief Clears the current selection.
75 ///
76 /// This function is called to remove the current selection of a document
77 /// page. If the page has no selection this function does nothing.
78 ///
79 void
clearSelection()80 DocumentPage::clearSelection ()
81 {
82     if ( m_HasSelection )
83     {
84         invertArea (m_SelectionX1, m_SelectionY1, m_SelectionX2, m_SelectionY2);
85         m_HasSelection = FALSE;
86     }
87 
88     if(NULL != m_Selection){
89         invertRegion(m_Selection);
90         gdk_region_destroy(m_Selection);
91         m_Selection = NULL;
92     }
93 }
94 
95 ///
96 /// @brief Gets the page's data.
97 ///
98 /// Gets the actual pixels the page is formed of. The format of the image
99 /// is RGB, using 3 bytes per pixel and a total of getWidth() * getHeight()
100 /// pixels.
101 ///
102 /// @return The page's pixels array.
103 ///
104 guchar *
getData()105 DocumentPage::getData ()
106 {
107     g_assert ( NULL != m_Data &&
108               "Tried to retrieve data without creating a new page.");
109 
110     return m_Data;
111 }
112 
113 ///
114 /// @brief Gets the link for a given position.
115 ///
116 /// This function will get the link whose rectangle is under the position
117 /// passed as parameter.
118 ///
119 /// @param x The X coordinate of the link's position.
120 /// @param y The Y coordinate of the link's position.
121 ///
122 /// @return The link under the position (x, y) or NULL if no link exists.
123 ///
124 IDocumentLink *
getLinkAtPosition(gint x,gint y)125 DocumentPage::getLinkAtPosition (gint x, gint y)
126 {
127     IDocumentLink *link = NULL;
128 
129     for ( GList *linkItem = g_list_first (m_LinkList) ;
130           NULL != linkItem && NULL == link;
131           linkItem = g_list_next (linkItem) )
132     {
133         IDocumentLink *tmpLink = (IDocumentLink *)linkItem->data;
134         if ( tmpLink->positionIsOver (x, y) )
135         {
136             link = tmpLink;
137         }
138     }
139 
140     return link;
141 }
142 
143 ///
144 /// @brief Gets the page's height.
145 ///
146 /// @return The height of the page's image.
147 ///
148 gint
getHeight()149 DocumentPage::getHeight ()
150 {
151     return m_Height;
152 }
153 
154 ///
155 /// @brief Gets the row stride.
156 ///
157 /// The row stride is the bytes between two consecutive rows of the page's
158 /// image retrieved by getData(). Currently this value is 3 * getWidth(),
159 /// because I use 3 bytes per pixel, but this can change, so better use this
160 /// function.
161 ///
162 /// @return The number of bytes between two consecutive rows.
163 ///
164 gint
getRowStride()165 DocumentPage::getRowStride ()
166 {
167 #if defined (HAVE_POPPLER_0_17_0)
168     return cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, getWidth ());
169 #else // !HAVE_POPPLER_0_17_0
170     return BYTES_PER_PIXEL * getWidth ();
171 #endif // HAVE_POPPLER_0_17_0
172 }
173 
174 ///
175 /// @brief Gets the page's width.
176 ///
177 /// @return The width of the page's image.
178 ///
179 gint
getWidth()180 DocumentPage::getWidth ()
181 {
182     return m_Width;
183 }
184 
185 ///
186 /// @brief Tells whether the page has an alpha component or not.
187 ///
188 /// This is mainly used to detect whether the data is 24 or 32 bits.  This is
189 /// important for pages rendered using cairo, because even though the format is
190 /// indeed RGB, the data is 32-bit always.
191 ///
192 /// @return @c TRUE if the document page has alpha, @c FALSE otherwise.
193 ///
194 gboolean
hasAlpha()195 DocumentPage::hasAlpha ()
196 {
197 #if defined (HAVE_POPPLER_0_17_0)
198     return TRUE;
199 #else // !HAVE_POPPLER_0_17_0
200     return FALSE;
201 #endif // HAVE_POPPLER_0_17_0
202 }
203 
204 ///
205 /// @brief Inverts the colours of a page's area.
206 ///
207 /// This function is used to mark or clear a selection to the document
208 /// page. It just inverts the colours of the rectangle area given as parameters.
209 ///
210 /// @param x1 The X coordinate of the area's top-right corner.
211 /// @param y1 The Y coordinate of the area's top-right corner.
212 /// @param x2 The X coordinate of the area's bottom-left corner.
213 /// @param y2 The Y coordinate of the area's bottom-left corner.
214 ///
215 void
invertArea(gint x1,gint y1,gint x2,gint y2)216 DocumentPage::invertArea (gint x1, gint y1, gint x2, gint y2)
217 {
218     gint rowStride = getRowStride ();
219     guchar *data = getData ();
220     for ( gint y = y1 ; y < y2 ; y++ )
221     {
222         for ( gint x = x1 ; x < x2 ; x++ )
223         {
224             gint position = y * rowStride + ( x * BYTES_PER_PIXEL );
225             data[position + 0] = 255 - data[position + 0];
226             data[position + 1] = 255 - data[position + 1];
227             data[position + 2] = 255 - data[position + 2];
228         }
229     }
230 }
231 
232 void
invertRegion(GdkRegion * region)233 DocumentPage::invertRegion (GdkRegion* region)
234 {
235     int count;
236     GdkRectangle *rectangles;
237     gdk_region_get_rectangles(region, &rectangles, &count);
238     while(count--){
239         GdkRectangle r = rectangles[count];
240         invertArea(r.x, r.y, r.x + r.width, r.y + r.height);
241     }
242     g_free(rectangles);
243 }
244 
245 
246 ///
247 /// @brief Allocates the memory for a new page.
248 ///
249 /// It also stores the @a width and @a height, to be retrieved by
250 /// calling getWidth() and getHeight() respectively.
251 ///
252 /// @param width The width of the new page.
253 /// @param height The height of the new page.
254 ///
255 /// @return true if the image could be created. False otherwise.
256 ///
257 gboolean
newPage(gint width,gint height)258 DocumentPage::newPage (gint width, gint height)
259 {
260     g_assert ( 1 <= width && "Tried to create a 0 width page.");
261     g_assert ( 1 <= height && "Tried to create a 0 height page.");
262 
263     m_Width = width;
264     m_Height = height;
265 
266     gint dataSize = height * getRowStride();
267     delete[] m_Data;
268     m_Data = new guchar[dataSize];
269     if ( NULL != m_Data )
270     {
271         memset (m_Data, 0xff, dataSize);
272     }
273 
274     return NULL != m_Data;
275 }
276 
277 ///
278 /// @brief Marks a selection on the page.
279 ///
280 /// This functions selects an area of the page as a selection and
281 /// modifies the image to make it visible. For now it does invert the
282 /// colours of the selection.
283 ///
284 /// This function also clears any previously set selection, if any.
285 ///
286 /// @param selection The selection to set on the page. The coordinates
287 ///                  are unscaled.
288 /// @param scale The scale to draw the selection.
289 ///
290 void
setSelection(DocumentRectangle & selection,gdouble scale)291 DocumentPage::setSelection (DocumentRectangle &selection, gdouble scale)
292 {
293     clearSelection ();
294 
295     m_SelectionX1 = MAX ((gint)(selection.getX1 () * scale - 0.5), 0);
296     m_SelectionX2 = MAX ((gint)(selection.getX2 () * scale + 0.5), 1);
297     m_SelectionY1 = MAX ((gint)(selection.getY1 () * scale - 0.5), 0);
298     m_SelectionY2 = MAX ((gint)(selection.getY2 () * scale + 0.5), 1);
299 
300     invertArea (m_SelectionX1, m_SelectionY1, m_SelectionX2, m_SelectionY2);
301 
302     m_HasSelection = TRUE;
303 }
304 
305 void
setSelection(GdkRegion * region)306 DocumentPage::setSelection (GdkRegion *region)
307 {
308     clearSelection ();
309 
310     invertRegion (region);
311 
312     m_Selection = gdk_region_copy(region);
313 }
314