libcrn  3.9.5
A document image processing library
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
CRNDocument.cpp
Go to the documentation of this file.
1 /* Copyright 2006-2016 Yann LEYDIER, INSA-Lyon, ENS-Lyon
2  *
3  * This file is part of libcrn.
4  *
5  * libcrn is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * libcrn is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with libcrn. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * file: CRNDocument.cpp
19  * \author Yann LEYDIER
20  */
21 
22 #include <CRNi18n.h>
23 #include <CRNDocument.h>
24 #include <CRNException.h>
25 #include <CRNConfig.h>
27 #include <CRNUtils/CRNProgress.h>
28 #include <CRNXml/CRNXml.h>
29 #include <CRNIO/CRNIO.h>
30 #ifdef CRN_USING_HARU
31 # include <CRNUtils/CRNPDF.h>
32 #endif
33 
34 using namespace crn;
35 using namespace crn::literals;
36 
37 const Path Document::thumbdir("/thumbs/");
38 size_t Document::thumbWidth = 70; // almost A4
39 size_t Document::thumbHeight = 100;
40 
41 /*****************************************************************************/
47  basename(""),
48  author(U""),
49  date(U"")
50 {
51 }
52 
53 /*****************************************************************************/
59 {
60 }
61 
62 /*****************************************************************************/
72 {
73  if (!fname)
74  throw ExceptionInvalidArgument(StringUTF8("void Document::AddView(const Path &fname): ") + _("null filename."));
75  String id(createNewId());
76  addView(fname, id);
77  return id;
78 }
79 
80 /*****************************************************************************/
87 void Document::addView(const Path &fname, const String &id)
88 {
89  views.push_back(view(fname, id));
90 }
91 
92 /*****************************************************************************/
103 String Document::InsertView(const Path &fname, size_t pos)
104 {
105  if (!fname)
106  throw ExceptionInvalidArgument(StringUTF8("void Document::InsertView(const Path &fname, size_t pos): ") + _("null filename."));
107  if (pos > views.size())
108  throw ExceptionDomain(StringUTF8("void Document::InsertView(const Path &fname, size_t pos): ") + _("index out of bounds."));
109 
110  if (pos == views.size())
111  return AddView(fname);
112  else
113  {
114  String id(createNewId());
115  views.insert(views.begin() + pos, view(fname, id));
116  return id;
117  }
118 }
119 
120 /*****************************************************************************/
130 void Document::RemoveView(const Path &fname)
131 {
132  view v(fname);
133  auto it(std::find(views.begin(), views.end(), v));
134  if (it == views.end())
135  throw ExceptionNotFound(StringUTF8("void Document::RemoveView(const Path &fname): ") + _("filename not found."));
136  RemoveView(it - views.begin());
137 }
138 
139 /*****************************************************************************/
147 void Document::RemoveView(size_t num)
148 {
149  if (num >= views.size())
150  throw ExceptionDomain(StringUTF8("void Document::RemoveView(size_t num): ") + _("index out of bounds."));
151  // remove thumbnail
152  Path thumbname(basename + thumbdir + views[num].id);
153  try
154  {
155  IO::Rm(thumbname);
156  } catch (...) { }
157  // remove xml
158  Path xmlname(basename + "/" + views[num].id + ".xml");
159  try
160  {
161  IO::Rm(xmlname);
162  } catch (...) { }
163  // remove block
164  views.erase(views.begin() + num);
165 }
166 
167 /*****************************************************************************/
176 {
178 }
179 
180 /*****************************************************************************/
195 SBlock Document::GetView(size_t num) const
196 {
197  if (num >= views.size())
198  {
199  throw ExceptionDomain(StringUTF8("SBlock Document::GetView(size_t num) const: ") + _("Index out of bounds."));
200  }
201  if (views[num].ptr.expired())
202  {
203  Path s;
204  if (basename.IsNotEmpty())
205  {
206  s += basename + Path::Separator();
207  s += Path(views[num].id) + ".xml";
208  }
209  SBlock b(Block::New(views[num].filename, s, views[num].filename));
210  views[num].ptr = b;
211  return b;
212  }
213  return views[num].ptr.lock();
214 }
215 
228 SBlock Document::GetView(const String &id) const
229 {
230  return GetView(GetViewIndex(id));
231 }
232 
245 SBlock Document::GetView(const Path &fname) const
246 {
247  return GetView(GetViewIndex(fname));
248 }
249 
258 size_t Document::GetViewIndex(const String &id) const
259 {
260  for (size_t tmp = 0; tmp < views.size(); ++tmp)
261  {
262  if (views[tmp].id == id)
263  return tmp;
264  }
265  throw ExceptionNotFound(StringUTF8("size_t Document::GetViewIndex(const String &id) const: ") + _("id not found."));
266 }
267 
276 size_t Document::GetViewIndex(const Path &fname) const
277 {
278  for (size_t tmp = 0; tmp < views.size(); ++tmp)
279  {
280  if (views[tmp].filename == fname)
281  return tmp;
282  }
283  throw ExceptionNotFound(StringUTF8("size_t Document::GetViewIndex(const Path &fname) const: ") + _("filename not found."));
284 }
285 
293 String Document::GetViewId(size_t num) const
294 {
295  if (num >= views.size())
296  throw ExceptionDomain(StringUTF8("const String Document::GetViewId(size_t num) const: ") + _("index out of bounds."));
297 
298  return views[num].id;
299 }
300 
308 String Document::GetViewId(const Path &fname) const
309 {
310  for (auto & elem : views)
311  {
312  if (elem.filename == fname)
313  return elem.id;
314  }
315  throw ExceptionNotFound(StringUTF8("const String Document::GetViewId(const Path &fname) const: ") + _("filename not found."));
316 }
317 
326 {
327  if (num >= views.size())
328  throw ExceptionDomain(StringUTF8("const Path Document::GetViewFilename(size_t num) const: ") + _("index out of bounds."));
329 
330  return views[num].filename;
331 }
332 
341 {
342  return GetViewFilename(GetViewIndex(id));
343 }
344 
355 void Document::ReorderViewsFrom(const std::vector<size_t> &from)
356 {
357  if (from.size() != views.size())
358  {
359  throw ExceptionDimension(StringUTF8("bool Document::ReorderViewsFrom(const std::vector<size_t> &from): ") +
360  _("bad changeset size."));
361  }
362  std::set<size_t> cont;
363  for (size_t i : from)
364  {
365  cont.insert(i);
366  }
367  if (cont.size() != views.size())
368  {
369  throw ExceptionLogic(StringUTF8("bool Document::ReorderViewsFrom(const std::vector<size_t> &from): ") +
370  _("changeset contains duplicates."));
371  }
372  if ((*cont.begin() != 0) || (*cont.rbegin() != views.size() - 1))
373  {
374  throw ExceptionDomain(StringUTF8("bool Document::ReorderViewsFrom(const std::vector<size_t> &from): ") +
375  _("changeset contains values out of bounds."));
376  }
377  std::vector<view> newviews;
378  for (size_t i : from)
379  {
380  newviews.push_back(views[i]);
381  }
382  views.swap(newviews);
383 }
384 
395 void Document::ReorderViewsTo(const std::vector<size_t> &to)
396 {
397  if (to.size() != views.size())
398  {
399  throw ExceptionDimension(StringUTF8("bool Document::ReorderViewsTo(const std::vector<size_t> &to): ") +
400  _("bad changeset size."));
401  }
402  std::set<size_t> cont;
403  for (size_t i : to)
404  {
405  cont.insert(i);
406  }
407  if (cont.size() != views.size())
408  {
409  throw ExceptionLogic(StringUTF8("bool Document::ReorderViewsTo(const std::vector<size_t> &to): ") +
410  _("changeset contains duplicates."));
411  }
412  if ((*cont.begin() != 0) || (*cont.rbegin() != views.size() - 1))
413  {
414  throw ExceptionDomain(StringUTF8("bool Document::ReorderViewsTo(const std::vector<size_t> &to): ") +
415  _("changeset contains values out of bounds."));
416  }
417  std::vector<view> newviews(views.size());
418  for (size_t tmp = 0; tmp < to.size(); ++tmp)
419  {
420  newviews[to[tmp]] = views[tmp];
421  }
422  views.swap(newviews);
423 }
424 
428 {
429  views.clear();
430  SetName(U"");
431  SetAuthor(U"");
432  SetDate(U"");
433  ClearUserData();
434 }
435 
436 /*****************************************************************************/
448 void Document::load(const Path &fname)
449 {
450  xml::Document doc(fname); // may throw
451  xml::Element root(doc.GetRoot()); // may throw
452  if (root.GetName() != "Document")
453  {
454  throw ExceptionRuntime(StringUTF8("bool Document::load(const String &fname): ") +
455  _("Not a Document file."));
456  }
457 
458  std::multimap<int, std::pair<Path, String> > xmlviews;
459  xml::Element vi(root.GetFirstChildElement("View"));
460  while (vi)
461  {
462  Path fname(vi.GetAttribute<StringUTF8>("fname", false)); // may throw
463  int num ;
464  try { num = vi.GetAttribute<int>("num", false); } catch (...) { num = int(xmlviews.size()); } // if no index found, push back
465  String id = vi.GetAttribute<StringUTF8>("id");
466  if (id.IsEmpty()) // if no id found, use index
467  id = num;
468  xmlviews.insert(std::make_pair(num, std::make_pair(fname, id)));
469  vi = vi.GetNextSiblingElement("View");
470  }
471 
472  StringUTF8 bn = root.GetAttribute<StringUTF8>("basename", false); // may throw
473  if (bn.IsNotEmpty())
474  {
475  basename = bn;
476  if (!IO::Access(bn, IO::EXISTS))
477  {
478  IO::Mkdir(bn); // may throw
479  }
480  }
481  bn = root.GetAttribute<StringUTF8>("author");
482  if (bn.IsNotEmpty())
483  author = bn;
484  bn = root.GetAttribute<StringUTF8>("date");
485  if (bn.IsNotEmpty())
486  date = bn;
487 
488  views.clear();
489  for (auto & xmlview : xmlviews)
490  {
491  addView(xmlview.second.first, xmlview.second.second);
492  }
493  /*
494  xml::Element vi(root.GetFirstChildElement("View"));
495  int cnt = 0;
496  while (vi)
497  {
498  bn = vi.GetAttribute<StringUTF8>("fname");
499  if (bn.IsNotEmpty())
500  {
501  const StringUTF8 id = vi.GetAttribute<StringUTF8>("id");
502  if (id.IsNotEmpty())
503  addView(bn, id);
504  else // if no id was found, create one using the index
505  addView(bn, String(cnt));
506  }
507  cnt += 1;
508  vi = vi.GetNextSiblingElement("View");
509  }
510  */
512 }
513 
519 {
520  return basename + thumbdir;
521 }
522 
531 UImage Document::createThumbnail(const Path &imagename) const
532 {
533  UImage img = NewImageFromFile(imagename);
534  // scale the image
535  size_t nw, nh;
536  nh = img->GetHeight() * thumbWidth / img->GetWidth();
537  if (nh <= thumbHeight)
538  {
539  nw = thumbWidth;
540  }
541  else
542  {
543  nw = img->GetWidth() * thumbHeight / img->GetHeight();
544  nh = thumbHeight;
545  }
546  img->ScaleToSize(nw, nh);
547  return std::forward<UImage>(img);
548 }
549 
560 UImage Document::GetThumbnail(size_t index, bool refresh) const
561 {
562  Path thumbname(GetThumbnailFilename(index, refresh));
563  return NewImageFromFile(thumbname);
564 }
565 
577 UImage Document::GetThumbnail(const String &id, bool refresh) const
578 {
579  return GetThumbnail(GetViewIndex(id), refresh);
580 }
581 
594 Path Document::GetThumbnailFilename(size_t index, bool refresh) const
595 {
596  if (index >= GetNbViews())
597  throw ExceptionDomain(StringUTF8("const Path Document::GetThumbnailFilename(size_t index, bool refresh) const: ") + _("index out of bounds."));
598 
599  if (!basename)
600  { // the document was never saved, so no thumbnail can be cached
601  throw ExceptionUninitialized(StringUTF8("const Path Document::GetThumbnailFilename(size_t index, bool refresh) const: ") + _("the document was never saved."));
602  }
603 
604  if (!IO::Access(basename + thumbdir, IO::EXISTS))
605  { // if the thumb directory does not exist, create it
606  IO::Mkdir(basename + thumbdir);
607  }
608 
609  Path thumbname(basename + thumbdir + GetViewId(index));
610  if (!IO::Access(thumbname, IO::EXISTS) || refresh)
611  { // compute the thumbnail
612  UImage img(createThumbnail(GetViewFilename(index)));
613  // scale the image
614  img->SavePNG(thumbname);
615  }
616  return thumbname;
617 }
618 
630 Path Document::GetThumbnailFilename(const String &id, bool refresh) const
631 {
632  return GetThumbnailFilename(GetViewIndex(id), refresh);
633 }
634 
635 /*****************************************************************************/
644 void Document::save(const Path &fname)
645 {
646  size_t namepos = fname.BackwardFindAnyOf("."); // strip extension if any
647  if (namepos == String::NPos())
648  namepos = 0;
649  basename = Path(fname.SubString(0, namepos)) + "_data"; // append "_data"
650  if (!IO::Access(basename, IO::EXISTS))
651  {
652  IO::Mkdir(basename); // may throw
653  }
654 
655  xml::Document doc;
656  doc.PushBackComment("libcrn Document file");
657  xml::Element root(doc.PushBackElement("Document"));
658  root.SetAttribute("basename", basename.CStr());
659  root.SetAttribute("author", author.CStr());
660  root.SetAttribute("date", date.CStr());
661 
662  // save views
663  for (size_t tmp = 0; tmp < views.size(); tmp++)
664  {
665  xml::Element el(root.PushBackElement("View"));
666  el.SetAttribute("fname", views[tmp].filename.CStr());
667  el.SetAttribute("id", views[tmp].id.CStr());
668  el.SetAttribute("num", int(tmp));
669  }
670 
672 
673  doc.Save(fname); // may throw
674 }
675 
682 {
683  const auto dirname = Config::GetTopDataPath() / "documents";
684  if (!IO::Access(dirname, IO::EXISTS))
685  {
686  try { IO::Mkdir(dirname); }
687  catch (...) {}
688  }
689  return dirname;
690 }
691 
692 /*****************************************************************************/
699 Path Document::completeFilename(const Path &fn) const
700 {
701  return GetDefaultDirName() / fn;
702 }
703 
709 std::vector<Path> Document::GetFilenames() const
710 {
711  std::vector<Path> flist;
712  for (const view &v : views)
713  flist.push_back(v.filename);
714  return flist;
715 }
716 
722 std::vector<String> Document::GetViewIds() const
723 {
724  std::vector<String> flist;
725  for (const view &v : views)
726  flist.push_back(v.id);
727  return flist;
728 }
729 
734 String Document::createNewId() const
735 {
736  // create a temporary file name (in std C)
738  // check if the id is unique
739  for (const view &v : views)
740  {
741  if (v.id == id)
742  return createNewId();
743  }
744  return id;
745 }
746 
747 #ifdef CRN_USING_HARU
748 
758 void Document::ExportPDF(const Path &fname, const PDF::Attributes &attr, Progress *prog) const
759 {
760  PDF::Doc pdf(attr);
761  std::vector<Path> images;
762  Path tmpimg(tmpnam(nullptr));
763  if (prog)
764  prog->SetMaxCount((int)GetNbViews());
765  for (const view &v : views)
766  {
767  PDF::Page page = pdf.AddPage();
768  PDF::Image image;
769  if (attr.lossy_compression)
770  { // jpeg
771  auto img = NewImageFromFile(v.filename);
772  img->SaveJPEG(tmpimg, attr.jpeg_qual);
773  image = pdf.AddJPEG(tmpimg);
774  }
775  else
776  { // png
777  auto img = NewImageFromFile(v.filename);
778  img->SavePNG(images.back());
779  images.push_back(tmpnam(nullptr));
780  image = pdf.AddPNG(images.back());
781  }
782  page.SetWidth((double)image.GetWidth());
783  page.SetHeight((double)image.GetHeight());
784  page.DrawImage(image, {0, 0, (int)image.GetWidth() - 1, (int)image.GetHeight() - 1});
785  if (prog)
786  prog->Advance();
787  }
788  // save
789  pdf.Save(fname);
790 
791  if (attr.lossy_compression)
792  {
793  try
794  {
795  IO::Rm(tmpimg);
796  } catch (...) { }
797  }
798  else
799  {
800  for (const Path &fname : images)
801  {
802  try
803  {
804  IO::Rm(fname);
805  } catch (...) { }
806  }
807  }
808 }
809 #endif
810 
811 size_t Document::GetThumbWidth() noexcept
812 {
813  return thumbWidth;
814 }
815 size_t Document::GetThumbHeight() noexcept
816 {
817  return thumbHeight;
818 }
820 {
821  if (w == 0)
822  throw ExceptionDomain{ "Document::SetThumbWidth()"_s + _("Null width.") };
823  thumbWidth = w;
824 }
826 {
827  if (h == 0)
828  throw ExceptionDomain{ "Document::SetThumbHeight()"_s + _("Null height.") };
829  thumbHeight = h;
830 }
void ReorderViewsTo(const std::vector< size_t > &to)
Reorders the views.
Comment PushBackComment(const StringUTF8 &text)
Adds a comment at the end of the children list.
Definition: CRNXml.cpp:1126
static void SetThumbHeight(size_t h)
static void SetThumbWidth(size_t w)
Base class for a progress display.
Definition: CRNProgress.h:39
XML element.
Definition: CRNXml.h:135
A generic runtime error.
Definition: CRNException.h:131
virtual ~Document() override
Destructor.
Definition: CRNDocument.cpp:58
Document()
Constructor.
Definition: CRNDocument.cpp:46
complex base abstract class
Definition: CRNSavable.h:58
void SetDate(const String &s)
Sets the date of the document.
Definition: CRNDocument.h:70
#define _(String)
Definition: CRNi18n.h:51
SBlock GetView(size_t num) const
Returns a pointer to a view.
std::unique_ptr< ImageBase > UImage
Definition: CRNImage.h:105
const char * CStr() const
Conversion to UTF8 cstring.
Definition: CRNString.cpp:167
Unintialized object error.
Definition: CRNException.h:155
XML document.
Definition: CRNXml.h:429
Element PushBackElement(const StringUTF8 &name)
Adds an element at the end of the children list.
Definition: CRNXml.cpp:1087
bool IsNotEmpty() const noexcept
Checks if the string is not empty.
static void Mkdir(const Path &name)
Creates a directory.
Definition: CRNIO.cpp:137
static char Separator() noexcept
Local directory separator.
Definition: CRNPath.cpp:42
std::vector< Path > GetFilenames() const
Gets the list of the image files of the document.
void serialize_internal_data(xml::Element &el) const
Dumps some internal data to an XML element.
Definition: CRNSavable.cpp:316
A generic logic error.
Definition: CRNException.h:71
void Advance()
Progresses of one step.
Definition: CRNProgress.cpp:34
A UTF32 character string class.
Definition: CRNString.h:61
static Path GetDefaultDirName()
Returns the default directory where the documents are saved.
Path GetThumbnailFilename(size_t index, bool refresh=false) const
Returns the filename of a thumbnail of a view (cached)
UImage GetThumbnail(size_t index, bool refresh=false) const
Returns a thumbnail of a view (cached)
String AddView(const Path &fname)
Adds a new image.
Definition: CRNDocument.cpp:71
A generic domain error.
Definition: CRNException.h:83
String InsertView(const Path &fname, size_t pos)
Inserts a new image.
StringUTF8 SubString(size_t pos, size_t n=0) const
Extracts a part of the string.
static SBlock New(const SImage &src, const String &nam=U"")
Top block creator.
Definition: CRNBlock.cpp:46
const char * CStr() const noexcept
Conversion to UTF8 cstring.
Path GetViewFilename(size_t num) const
Returns the filename of a view.
A convenience class for file paths.
Definition: CRNPath.h:39
static size_t GetThumbHeight() noexcept
void ClearUserData()
Deletes all user data entries.
Definition: CRNSavable.cpp:155
Path GetThumbnailPath() const
Returns the path of the thumbnails.
static String CreateUniqueId(size_t len=8)
Generates an almost unique id.
Definition: CRNString.cpp:799
A dimension error.
Definition: CRNException.h:119
String GetViewId(size_t num) const
Returns the id of a view.
void SetAttribute(const StringUTF8 &name, const StringUTF8 &value)
Sets the value of an attribute.
Definition: CRNXml.cpp:595
void Save(const Path &fname)
Saves to file.
Definition: CRNXml.cpp:1009
static size_t GetThumbWidth() noexcept
void SetMaxCount(size_t maxcount, bool reset=true)
Sets the total number of steps.
Definition: CRNProgress.h:56
static Path GetTopDataPath()
Gets the top directory name.
Definition: CRNConfig.cpp:144
void RemoveView(const Path &fname)
Removes a view.
UImage NewImageFromFile(const Path &fname)
Loads an image from a file.
Definition: CRNImage.cpp:609
void SetName(const String &s)
Sets the name of the object.
Definition: CRNSavable.h:80
Attributes to create a PDF export of a document.
size_t GetViewIndex(const String &id) const
Returns the index of a view.
static void Rm(const Path &name)
Removes a file.
Definition: CRNIO.cpp:175
static bool Access(const Path &name, int mode)
Checks rights on a file.
Definition: CRNIO.cpp:161
size_t GetNbViews() const noexcept
Returns the number of views.
Definition: CRNDocument.h:112
void Clear()
Removes all views and unsets all data.
A character string class.
Definition: CRNStringUTF8.h:49
void ReorderViewsFrom(const std::vector< size_t > &from)
Reorders the views.
void SetAuthor(const String &s)
Sets the author of the document.
Definition: CRNDocument.h:68
std::vector< String > GetViewIds() const
Gets the list of the view ids of the document.
size_t BackwardFindAnyOf(const StringUTF8 &s, size_t from_pos=NPos()) const
Finds the last occurrence of character in a list.
void deserialize_internal_data(xml::Element &el)
Initializes some internal data from an XML element.
Definition: CRNSavable.cpp:282
Invalid argument error (e.g.: nullptr pointer)
Definition: CRNException.h:107
static size_t NPos() noexcept
Last position in a string.
Definition: CRNString.cpp:47
An item was not found in a container.
Definition: CRNException.h:95